funifier-mcp 0.3.16 → 0.3.18
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/.cursor/rules/funifier.mdc +1 -0
- package/.github/copilot-instructions.md +1 -0
- package/AGENTS.md +1 -0
- package/datasource-funifier-docs/.coverage.json +12 -5
- package/datasource-funifier-docs/.search-index.json +59345 -0
- package/datasource-funifier-docs/.skills-map.json +145 -0
- package/datasource-funifier-docs/.validation.json +38 -2
- package/datasource-funifier-docs/knowledge/guides/permission-audit.md +229 -0
- package/datasource-funifier-docs/knowledge/index.md +1 -0
- package/dist/mcp/bundle.js +108 -108
- package/dist/mcp/tools/_audit.d.ts +103 -0
- package/dist/mcp/tools/_audit.d.ts.map +1 -0
- package/dist/mcp/tools/_audit.js +241 -0
- package/dist/mcp/tools/_audit.js.map +1 -0
- package/dist/mcp/tools/_audit.test.d.ts +2 -0
- package/dist/mcp/tools/_audit.test.d.ts.map +1 -0
- package/dist/mcp/tools/_audit.test.js +412 -0
- package/dist/mcp/tools/_audit.test.js.map +1 -0
- package/dist/mcp/tools/_backup.d.ts +37 -3
- package/dist/mcp/tools/_backup.d.ts.map +1 -1
- package/dist/mcp/tools/_backup.js +137 -8
- package/dist/mcp/tools/_backup.js.map +1 -1
- package/dist/mcp/tools/_backup.test.js +195 -0
- package/dist/mcp/tools/_backup.test.js.map +1 -1
- package/dist/mcp/tools/_scope-engine.d.ts +40 -0
- package/dist/mcp/tools/_scope-engine.d.ts.map +1 -0
- package/dist/mcp/tools/_scope-engine.js +197 -0
- package/dist/mcp/tools/_scope-engine.js.map +1 -0
- package/dist/mcp/tools/_scope-engine.test.d.ts +2 -0
- package/dist/mcp/tools/_scope-engine.test.d.ts.map +1 -0
- package/dist/mcp/tools/_scope-engine.test.js +241 -0
- package/dist/mcp/tools/_scope-engine.test.js.map +1 -0
- package/dist/mcp/tools/permissions.d.ts.map +1 -1
- package/dist/mcp/tools/permissions.js +68 -11
- package/dist/mcp/tools/permissions.js.map +1 -1
- package/dist/mcp/tools/permissions.test.js +268 -4
- package/dist/mcp/tools/permissions.test.js.map +1 -1
- package/package.json +1 -1
- package/skills/funifier/SKILL.md +1 -0
- package/skills/funifier/references/audit-permissions.md +89 -0
- package/skills/funifier/references/configure-security.md +0 -6
- package/skills/funifier/references/create-action.md +0 -7
- package/skills/funifier/references/create-aggregate.md +3 -7
- package/skills/funifier/references/create-audit.md +0 -8
- package/skills/funifier/references/create-challenge.md +0 -7
- package/skills/funifier/references/create-competition.md +0 -7
- package/skills/funifier/references/create-crossword.md +0 -6
- package/skills/funifier/references/create-custom-object.md +0 -6
- package/skills/funifier/references/create-custom-page.md +0 -6
- package/skills/funifier/references/create-folder.md +0 -7
- package/skills/funifier/references/create-lastmile.md +0 -6
- package/skills/funifier/references/create-leaderboard.md +0 -6
- package/skills/funifier/references/create-level.md +0 -7
- package/skills/funifier/references/create-lottery.md +0 -7
- package/skills/funifier/references/create-mystery.md +0 -6
- package/skills/funifier/references/create-notification.md +0 -6
- package/skills/funifier/references/create-point.md +0 -7
- package/skills/funifier/references/create-quiz.md +0 -7
- package/skills/funifier/references/create-scheduler.md +0 -6
- package/skills/funifier/references/create-story.md +0 -6
- package/skills/funifier/references/create-swap.md +0 -6
- package/skills/funifier/references/create-trigger.md +0 -8
- package/skills/funifier/references/create-virtual-good.md +0 -6
- package/skills/funifier/references/create-webhook.md +0 -6
- package/skills/funifier/references/create-websocket.md +0 -6
- package/skills/funifier/references/create-widget.md +0 -6
- package/skills/funifier/references/date-handling.md +0 -6
- package/skills/funifier/references/debug.md +0 -6
- package/skills/funifier/references/help.md +0 -6
- package/skills/funifier/references/implement-frontend.md +0 -7
- package/skills/funifier/references/import-csv.md +0 -6
- package/skills/funifier/references/manage-indexes.md +0 -6
- package/skills/funifier/references/manage-player.md +0 -7
- package/skills/funifier/references/manage-team.md +0 -6
- package/skills/funifier/references/upload-file.md +0 -6
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pure authorization engine mirroring SecurityFilter.doFilter from funifier-service.
|
|
4
|
+
* No I/O, no imports beyond types — fully unit-testable against a transcribed table.
|
|
5
|
+
*
|
|
6
|
+
* Source: SecurityFilter.java (funifier-service)
|
|
7
|
+
* Each rule carries a line reference so source drift can be detected via validate:skills claims.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.MANUAL_REVIEW_TOKENS = void 0;
|
|
11
|
+
exports.entityFull = entityFull;
|
|
12
|
+
exports.verbFor = verbFor;
|
|
13
|
+
exports.isPublicPath = isPublicPath;
|
|
14
|
+
exports.evaluateScope = evaluateScope;
|
|
15
|
+
exports.usedTokens = usedTokens;
|
|
16
|
+
/**
|
|
17
|
+
* Non-path-auditable tokens: their usage cannot be proven or disproven from a path manifest.
|
|
18
|
+
* These are always classified `manual-review`, never `excess`.
|
|
19
|
+
*/
|
|
20
|
+
exports.MANUAL_REVIEW_TOKENS = new Set([
|
|
21
|
+
"cross_domain",
|
|
22
|
+
"read_encrypted_field_values",
|
|
23
|
+
"read_encrypted_player_password",
|
|
24
|
+
"write_upload",
|
|
25
|
+
]);
|
|
26
|
+
// SecurityFilter.java:108-151: paths that bypass the scope check entirely.
|
|
27
|
+
// GET /v3/widget, /v3/global, /v3/system/global require no scope.
|
|
28
|
+
// NOTE: this list is hardcoded by mirroring SecurityFilter source; validate via sourceClaims.
|
|
29
|
+
const PUBLIC_GET_PREFIXES = ["/v3/widget", "/v3/global", "/v3/system/global"];
|
|
30
|
+
/** SecurityFilter.java:85-86: strip "/v3/", replace "/" → "_". */
|
|
31
|
+
function entityFull(path) {
|
|
32
|
+
const clean = path.split("?")[0];
|
|
33
|
+
const withoutV3 = clean.startsWith("/v3/") ? clean.slice(4) : clean.replace(/^\//, "");
|
|
34
|
+
return withoutV3.replace(/\//g, "_");
|
|
35
|
+
}
|
|
36
|
+
/** SecurityFilter.java:226-227: GET→read, POST/PUT→write, else lowercase. */
|
|
37
|
+
function verbFor(method) {
|
|
38
|
+
if (method === "GET")
|
|
39
|
+
return "read";
|
|
40
|
+
if (method === "POST" || method === "PUT")
|
|
41
|
+
return "write";
|
|
42
|
+
return method.toLowerCase();
|
|
43
|
+
}
|
|
44
|
+
/** SecurityFilter.java:108-151: true when the path requires no scope. */
|
|
45
|
+
function isPublicPath(method, path) {
|
|
46
|
+
const clean = path.split("?")[0];
|
|
47
|
+
if (method === "GET") {
|
|
48
|
+
for (const prefix of PUBLIC_GET_PREFIXES) {
|
|
49
|
+
if (clean === prefix || clean.startsWith(prefix + "/"))
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
/** SecurityFilter.java:223-224: remove spaces, split on comma, drop empty tokens. */
|
|
56
|
+
function normalizeScope(scope) {
|
|
57
|
+
return scope.replace(/ /g, "").split(",").filter(Boolean);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Evaluate whether `scope` authorizes `method` on `path`, reproducing the exact decision
|
|
61
|
+
* tree of SecurityFilter.doFilter.
|
|
62
|
+
*/
|
|
63
|
+
function evaluateScope(scope, method, path) {
|
|
64
|
+
const cleanPath = path.split("?")[0];
|
|
65
|
+
// SecurityFilter.java:108-151: public-path bypass
|
|
66
|
+
if (isPublicPath(method, cleanPath)) {
|
|
67
|
+
return { allowed: true, rule: "public-path", requiredTokens: [] };
|
|
68
|
+
}
|
|
69
|
+
const verb = verbFor(method);
|
|
70
|
+
const entity = entityFull(cleanPath);
|
|
71
|
+
const tokens = normalizeScope(scope);
|
|
72
|
+
const isDatabase = cleanPath === "/v3/database" || cleanPath.startsWith("/v3/database/");
|
|
73
|
+
// SecurityFilter.java:259-263: special POST exceptions (checked before grammar)
|
|
74
|
+
if (method === "POST") {
|
|
75
|
+
if (cleanPath === "/v3/action/log") {
|
|
76
|
+
if (tokens.includes("write_actionlog")) {
|
|
77
|
+
return {
|
|
78
|
+
allowed: true,
|
|
79
|
+
rule: "special-actionlog",
|
|
80
|
+
matchedToken: "write_actionlog",
|
|
81
|
+
requiredTokens: ["write_actionlog"],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// write_actionlog absent — falls through to grammar
|
|
85
|
+
}
|
|
86
|
+
if (cleanPath === "/v3/mobile/device") {
|
|
87
|
+
// Any non-empty scope passes (SecurityFilter.java:262-263)
|
|
88
|
+
if (tokens.length > 0) {
|
|
89
|
+
return { allowed: true, rule: "special-mobile-device", matchedToken: tokens[0], requiredTokens: [] };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// SecurityFilter.java:230-253: grammar allow rules — {verb}_all, exact, fallback m1, m2
|
|
94
|
+
let grammarAllowed = false;
|
|
95
|
+
let grammarRule = "denied";
|
|
96
|
+
let grammarToken;
|
|
97
|
+
let grammarRequired = [];
|
|
98
|
+
// 1. {verb}_all (SecurityFilter.java:230)
|
|
99
|
+
const verbAll = `${verb}_all`;
|
|
100
|
+
if (tokens.includes(verbAll)) {
|
|
101
|
+
grammarAllowed = true;
|
|
102
|
+
grammarRule = "all";
|
|
103
|
+
grammarToken = verbAll;
|
|
104
|
+
grammarRequired = [verbAll];
|
|
105
|
+
}
|
|
106
|
+
// 2. Exact: {verb}_{entity_full} (SecurityFilter.java:234)
|
|
107
|
+
if (!grammarAllowed) {
|
|
108
|
+
const exactToken = `${verb}_${entity}`;
|
|
109
|
+
if (tokens.includes(exactToken)) {
|
|
110
|
+
grammarAllowed = true;
|
|
111
|
+
grammarRule = "exact";
|
|
112
|
+
grammarToken = exactToken;
|
|
113
|
+
grammarRequired = [exactToken];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// 3. Fallback minus-1: {verb}_{prefix}_all (SecurityFilter.java:238-246, allow_full_m1)
|
|
117
|
+
if (!grammarAllowed) {
|
|
118
|
+
const segs = entity.split("_");
|
|
119
|
+
if (segs.length > 1) {
|
|
120
|
+
const m1Token = `${verb}_${segs.slice(0, -1).join("_")}_all`;
|
|
121
|
+
if (tokens.includes(m1Token)) {
|
|
122
|
+
grammarAllowed = true;
|
|
123
|
+
grammarRule = "fallback-m1";
|
|
124
|
+
grammarToken = m1Token;
|
|
125
|
+
grammarRequired = [m1Token];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// 4. Fallback minus-2: {verb}_{prefix}_all (SecurityFilter.java:247-253, allow_full_m2)
|
|
130
|
+
// Code stops at m2 despite the "menos n" comment — confirmed at source.
|
|
131
|
+
if (!grammarAllowed) {
|
|
132
|
+
const segs = entity.split("_");
|
|
133
|
+
if (segs.length > 2) {
|
|
134
|
+
const m2Token = `${verb}_${segs.slice(0, -2).join("_")}_all`;
|
|
135
|
+
if (tokens.includes(m2Token)) {
|
|
136
|
+
grammarAllowed = true;
|
|
137
|
+
grammarRule = "fallback-m2";
|
|
138
|
+
grammarToken = m2Token;
|
|
139
|
+
grammarRequired = [m2Token];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// DatabaseRest.java: every /v3/database handler additionally requires the literal
|
|
144
|
+
// "database" scope token regardless of which grammar rule would otherwise allow it.
|
|
145
|
+
// Applies to ALL verbs including GET — verified at all 13 DatabaseRest check sites.
|
|
146
|
+
if (isDatabase) {
|
|
147
|
+
if (grammarAllowed && !tokens.includes("database")) {
|
|
148
|
+
return {
|
|
149
|
+
allowed: false,
|
|
150
|
+
rule: "database-keyword-missing",
|
|
151
|
+
matchedToken: grammarToken,
|
|
152
|
+
requiredTokens: [...grammarRequired, "database"],
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (grammarAllowed) {
|
|
156
|
+
return {
|
|
157
|
+
allowed: true,
|
|
158
|
+
rule: grammarRule,
|
|
159
|
+
matchedToken: grammarToken,
|
|
160
|
+
requiredTokens: [...grammarRequired, "database"],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// Grammar denied on a database path: suggest exact token + database keyword
|
|
164
|
+
return {
|
|
165
|
+
allowed: false,
|
|
166
|
+
rule: "denied",
|
|
167
|
+
requiredTokens: [`${verb}_${entity}`, "database"],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (grammarAllowed) {
|
|
171
|
+
return { allowed: true, rule: grammarRule, matchedToken: grammarToken, requiredTokens: grammarRequired };
|
|
172
|
+
}
|
|
173
|
+
return { allowed: false, rule: "denied", requiredTokens: [`${verb}_${entity}`] };
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Returns the subset of tokens in `scope` that actually authorized at least one entry
|
|
177
|
+
* in `entries`. Used for excess-token calculation in the audit diff.
|
|
178
|
+
*/
|
|
179
|
+
function usedTokens(scope, entries) {
|
|
180
|
+
const tokens = normalizeScope(scope);
|
|
181
|
+
const used = new Set();
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
const decision = evaluateScope(scope, entry.method, entry.path);
|
|
184
|
+
if (decision.allowed) {
|
|
185
|
+
if (decision.matchedToken)
|
|
186
|
+
used.add(decision.matchedToken);
|
|
187
|
+
// "database" is a co-required token for /v3/database paths — mark it used too
|
|
188
|
+
const cleanPath = entry.path.split("?")[0];
|
|
189
|
+
if ((cleanPath === "/v3/database" || cleanPath.startsWith("/v3/database/")) &&
|
|
190
|
+
tokens.includes("database")) {
|
|
191
|
+
used.add("database");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return used;
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=_scope-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_scope-engine.js","sourceRoot":"","sources":["../../../src/mcp/tools/_scope-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_scope-engine.test.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/_scope-engine.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const _scope_engine_1 = require("./_scope-engine");
|
|
5
|
+
// ─── entityFull ───────────────────────────────────────────────────────────────
|
|
6
|
+
(0, vitest_1.describe)("entityFull", () => {
|
|
7
|
+
(0, vitest_1.it)("strips /v3/ and replaces / with _", () => {
|
|
8
|
+
(0, vitest_1.expect)((0, _scope_engine_1.entityFull)("/v3/database/mycoll/aggregate")).toBe("database_mycoll_aggregate");
|
|
9
|
+
(0, vitest_1.expect)((0, _scope_engine_1.entityFull)("/v3/action/log")).toBe("action_log");
|
|
10
|
+
(0, vitest_1.expect)((0, _scope_engine_1.entityFull)("/v3/game")).toBe("game");
|
|
11
|
+
(0, vitest_1.expect)((0, _scope_engine_1.entityFull)("/v3/game/level/experience")).toBe("game_level_experience");
|
|
12
|
+
});
|
|
13
|
+
(0, vitest_1.it)("strips query string before processing", () => {
|
|
14
|
+
(0, vitest_1.expect)((0, _scope_engine_1.entityFull)("/v3/action/log?limit=10")).toBe("action_log");
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
// ─── verbFor ─────────────────────────────────────────────────────────────────
|
|
18
|
+
(0, vitest_1.describe)("verbFor (SecurityFilter.java:226-227)", () => {
|
|
19
|
+
(0, vitest_1.it)("maps GET → read", () => (0, vitest_1.expect)((0, _scope_engine_1.verbFor)("GET")).toBe("read"));
|
|
20
|
+
(0, vitest_1.it)("maps POST → write", () => (0, vitest_1.expect)((0, _scope_engine_1.verbFor)("POST")).toBe("write"));
|
|
21
|
+
(0, vitest_1.it)("maps PUT → write", () => (0, vitest_1.expect)((0, _scope_engine_1.verbFor)("PUT")).toBe("write"));
|
|
22
|
+
(0, vitest_1.it)("maps DELETE → delete", () => (0, vitest_1.expect)((0, _scope_engine_1.verbFor)("DELETE")).toBe("delete"));
|
|
23
|
+
(0, vitest_1.it)("lowercases other methods", () => (0, vitest_1.expect)((0, _scope_engine_1.verbFor)("PATCH")).toBe("patch"));
|
|
24
|
+
});
|
|
25
|
+
// ─── isPublicPath ─────────────────────────────────────────────────────────────
|
|
26
|
+
(0, vitest_1.describe)("isPublicPath (SecurityFilter.java:108-151)", () => {
|
|
27
|
+
(0, vitest_1.it)("GET /v3/widget is public", () => (0, vitest_1.expect)((0, _scope_engine_1.isPublicPath)("GET", "/v3/widget")).toBe(true));
|
|
28
|
+
(0, vitest_1.it)("GET /v3/widget/my-widget is public (prefix match)", () => (0, vitest_1.expect)((0, _scope_engine_1.isPublicPath)("GET", "/v3/widget/my-widget")).toBe(true));
|
|
29
|
+
(0, vitest_1.it)("GET /v3/global is public", () => (0, vitest_1.expect)((0, _scope_engine_1.isPublicPath)("GET", "/v3/global")).toBe(true));
|
|
30
|
+
(0, vitest_1.it)("GET /v3/system/global is public", () => (0, vitest_1.expect)((0, _scope_engine_1.isPublicPath)("GET", "/v3/system/global")).toBe(true));
|
|
31
|
+
(0, vitest_1.it)("POST /v3/widget is NOT public (method mismatch)", () => (0, vitest_1.expect)((0, _scope_engine_1.isPublicPath)("POST", "/v3/widget")).toBe(false));
|
|
32
|
+
(0, vitest_1.it)("GET /v3/game is not a public path", () => (0, vitest_1.expect)((0, _scope_engine_1.isPublicPath)("GET", "/v3/game")).toBe(false));
|
|
33
|
+
});
|
|
34
|
+
// ─── evaluateScope — grammar rules ───────────────────────────────────────────
|
|
35
|
+
(0, vitest_1.describe)("evaluateScope — public-path bypass", () => {
|
|
36
|
+
(0, vitest_1.it)("allows GET /v3/widget regardless of scope", () => {
|
|
37
|
+
const d = (0, _scope_engine_1.evaluateScope)("", "GET", "/v3/widget");
|
|
38
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
39
|
+
(0, vitest_1.expect)(d.rule).toBe("public-path");
|
|
40
|
+
});
|
|
41
|
+
(0, vitest_1.it)("allows GET /v3/global regardless of scope", () => {
|
|
42
|
+
const d = (0, _scope_engine_1.evaluateScope)("", "GET", "/v3/global");
|
|
43
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
44
|
+
(0, vitest_1.expect)(d.rule).toBe("public-path");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
(0, vitest_1.describe)("evaluateScope — _all rule (SecurityFilter.java:230)", () => {
|
|
48
|
+
(0, vitest_1.it)("read_all allows GET on any non-database path", () => {
|
|
49
|
+
const d = (0, _scope_engine_1.evaluateScope)("read_all", "GET", "/v3/game/level");
|
|
50
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
51
|
+
(0, vitest_1.expect)(d.rule).toBe("all");
|
|
52
|
+
(0, vitest_1.expect)(d.matchedToken).toBe("read_all");
|
|
53
|
+
});
|
|
54
|
+
(0, vitest_1.it)("write_all allows POST on any non-database path", () => {
|
|
55
|
+
const d = (0, _scope_engine_1.evaluateScope)("write_all", "POST", "/v3/action/log");
|
|
56
|
+
// /v3/action/log POST: write_actionlog fires first — but write_all doesn't include write_actionlog, so grammar fires
|
|
57
|
+
// Actually write_all should be checked in grammar, but /v3/action/log is a special POST:
|
|
58
|
+
// write_actionlog is checked first; if not present, grammar runs. write_all would match grammar.
|
|
59
|
+
// BUT wait: the special-actionlog check only fires when write_actionlog IS present.
|
|
60
|
+
// So with scope=write_all, grammar check runs: write_all → allowed via "all" rule.
|
|
61
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
62
|
+
(0, vitest_1.expect)(d.rule).toBe("all");
|
|
63
|
+
});
|
|
64
|
+
(0, vitest_1.it)("delete_all allows DELETE on any non-database path", () => {
|
|
65
|
+
const d = (0, _scope_engine_1.evaluateScope)("delete_all", "DELETE", "/v3/game/level");
|
|
66
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
67
|
+
(0, vitest_1.expect)(d.rule).toBe("all");
|
|
68
|
+
(0, vitest_1.expect)(d.matchedToken).toBe("delete_all");
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
(0, vitest_1.describe)("evaluateScope — exact match (SecurityFilter.java:234)", () => {
|
|
72
|
+
(0, vitest_1.it)("read_action_log allows GET /v3/action/log", () => {
|
|
73
|
+
const d = (0, _scope_engine_1.evaluateScope)("read_action_log", "GET", "/v3/action/log");
|
|
74
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
75
|
+
(0, vitest_1.expect)(d.rule).toBe("exact");
|
|
76
|
+
(0, vitest_1.expect)(d.matchedToken).toBe("read_action_log");
|
|
77
|
+
});
|
|
78
|
+
(0, vitest_1.it)("write_game_level allows POST /v3/game/level", () => {
|
|
79
|
+
const d = (0, _scope_engine_1.evaluateScope)("write_game_level", "POST", "/v3/game/level");
|
|
80
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
81
|
+
(0, vitest_1.expect)(d.rule).toBe("exact");
|
|
82
|
+
});
|
|
83
|
+
(0, vitest_1.it)("denies when the exact token is absent", () => {
|
|
84
|
+
const d = (0, _scope_engine_1.evaluateScope)("read_game", "GET", "/v3/game/level");
|
|
85
|
+
(0, vitest_1.expect)(d.allowed).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
(0, vitest_1.describe)("evaluateScope — fallback minus-1 (allow_full_m1, SecurityFilter.java:238-246)", () => {
|
|
89
|
+
(0, vitest_1.it)("read_game_all allows GET /v3/game/level (entity=game_level, m1=game_all)", () => {
|
|
90
|
+
const d = (0, _scope_engine_1.evaluateScope)("read_game_all", "GET", "/v3/game/level");
|
|
91
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
92
|
+
(0, vitest_1.expect)(d.rule).toBe("fallback-m1");
|
|
93
|
+
(0, vitest_1.expect)(d.matchedToken).toBe("read_game_all");
|
|
94
|
+
});
|
|
95
|
+
(0, vitest_1.it)("read_game_level_all allows GET /v3/game/level/experience (m1=game_level_all)", () => {
|
|
96
|
+
const d = (0, _scope_engine_1.evaluateScope)("read_game_level_all", "GET", "/v3/game/level/experience");
|
|
97
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
98
|
+
(0, vitest_1.expect)(d.rule).toBe("fallback-m1");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
(0, vitest_1.describe)("evaluateScope — fallback minus-2 (allow_full_m2, SecurityFilter.java:247-253)", () => {
|
|
102
|
+
(0, vitest_1.it)("read_game_all allows GET /v3/game/level/experience (entity=game_level_experience, m2=game_all)", () => {
|
|
103
|
+
const d = (0, _scope_engine_1.evaluateScope)("read_game_all", "GET", "/v3/game/level/experience");
|
|
104
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
105
|
+
(0, vitest_1.expect)(d.rule).toBe("fallback-m2");
|
|
106
|
+
(0, vitest_1.expect)(d.matchedToken).toBe("read_game_all");
|
|
107
|
+
});
|
|
108
|
+
(0, vitest_1.it)("read_database_all does NOT authorize GET /v3/database/coll/find without database keyword (m2 denied first by db-keyword)", () => {
|
|
109
|
+
// read_database_all would be m1 for database_coll_find (entity has 3 segments)
|
|
110
|
+
// But /v3/database path requires "database" keyword regardless of grammar
|
|
111
|
+
const d = (0, _scope_engine_1.evaluateScope)("read_database_all", "GET", "/v3/database/coll/find");
|
|
112
|
+
(0, vitest_1.expect)(d.allowed).toBe(false);
|
|
113
|
+
(0, vitest_1.expect)(d.rule).toBe("database-keyword-missing");
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
(0, vitest_1.describe)("evaluateScope — beyond minus-2 denied", () => {
|
|
117
|
+
(0, vitest_1.it)("read_a_all does NOT authorize GET /v3/a/b/c/d (entity=a_b_c_d, m2=a_b, no token)", () => {
|
|
118
|
+
// m1 would be read_a_b_c_all, m2 would be read_a_b_all — neither in scope
|
|
119
|
+
const d = (0, _scope_engine_1.evaluateScope)("read_a_all", "GET", "/v3/a/b/c/d");
|
|
120
|
+
(0, vitest_1.expect)(d.allowed).toBe(false);
|
|
121
|
+
(0, vitest_1.expect)(d.rule).toBe("denied");
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
// ─── evaluateScope — special POST exceptions (SecurityFilter.java:259-263) ───
|
|
125
|
+
(0, vitest_1.describe)("evaluateScope — special POST exceptions", () => {
|
|
126
|
+
(0, vitest_1.it)("POST /v3/action/log with write_actionlog → special-actionlog (PSEC-06)", () => {
|
|
127
|
+
const d = (0, _scope_engine_1.evaluateScope)("write_actionlog", "POST", "/v3/action/log");
|
|
128
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
129
|
+
(0, vitest_1.expect)(d.rule).toBe("special-actionlog");
|
|
130
|
+
(0, vitest_1.expect)(d.matchedToken).toBe("write_actionlog");
|
|
131
|
+
(0, vitest_1.expect)(d.requiredTokens).toContain("write_actionlog");
|
|
132
|
+
});
|
|
133
|
+
(0, vitest_1.it)("POST /v3/action/log without write_actionlog falls through to grammar (denied if no other token)", () => {
|
|
134
|
+
const d = (0, _scope_engine_1.evaluateScope)("read_all", "POST", "/v3/action/log");
|
|
135
|
+
// grammar: write_all not present, exact write_action_log not present... read_all is for GET.
|
|
136
|
+
// Actually: verb for POST is "write". write_all not in scope. exact write_action_log not in scope.
|
|
137
|
+
// Falls through → denied (read_all doesn't authorize write)
|
|
138
|
+
(0, vitest_1.expect)(d.allowed).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
(0, vitest_1.it)("POST /v3/mobile/device with any non-empty scope → special-mobile-device", () => {
|
|
141
|
+
const d = (0, _scope_engine_1.evaluateScope)("read_all", "POST", "/v3/mobile/device");
|
|
142
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
143
|
+
(0, vitest_1.expect)(d.rule).toBe("special-mobile-device");
|
|
144
|
+
});
|
|
145
|
+
(0, vitest_1.it)("POST /v3/mobile/device with empty scope → denied (falls through to grammar)", () => {
|
|
146
|
+
const d = (0, _scope_engine_1.evaluateScope)("", "POST", "/v3/mobile/device");
|
|
147
|
+
(0, vitest_1.expect)(d.allowed).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
// ─── evaluateScope — scope normalization (SecurityFilter.java:223-224) ────────
|
|
151
|
+
(0, vitest_1.describe)("evaluateScope — scope normalization", () => {
|
|
152
|
+
(0, vitest_1.it)("removes spaces before splitting (space-separated scope string)", () => {
|
|
153
|
+
const d = (0, _scope_engine_1.evaluateScope)("read_all, write_all", "GET", "/v3/game");
|
|
154
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
155
|
+
(0, vitest_1.expect)(d.matchedToken).toBe("read_all");
|
|
156
|
+
});
|
|
157
|
+
(0, vitest_1.it)("handles duplicate tokens without false denials", () => {
|
|
158
|
+
const d = (0, _scope_engine_1.evaluateScope)("read_game,read_game,read_all", "GET", "/v3/game");
|
|
159
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
// ─── T6: database keyword rule (DatabaseRest.java, all 13 handler sites) ─────
|
|
163
|
+
(0, vitest_1.describe)("evaluateScope — database keyword (PSEC-07)", () => {
|
|
164
|
+
(0, vitest_1.it)("GET /v3/database/coll with read_all but without database keyword → database-keyword-missing", () => {
|
|
165
|
+
const d = (0, _scope_engine_1.evaluateScope)("read_all", "GET", "/v3/database/coll");
|
|
166
|
+
(0, vitest_1.expect)(d.allowed).toBe(false);
|
|
167
|
+
(0, vitest_1.expect)(d.rule).toBe("database-keyword-missing");
|
|
168
|
+
(0, vitest_1.expect)(d.requiredTokens).toContain("database");
|
|
169
|
+
(0, vitest_1.expect)(d.requiredTokens).toContain("read_all");
|
|
170
|
+
});
|
|
171
|
+
(0, vitest_1.it)("GET /v3/database/coll with read_all AND database keyword → allowed", () => {
|
|
172
|
+
const d = (0, _scope_engine_1.evaluateScope)("read_all,database", "GET", "/v3/database/coll");
|
|
173
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
174
|
+
(0, vitest_1.expect)(d.requiredTokens).toContain("database");
|
|
175
|
+
(0, vitest_1.expect)(d.requiredTokens).toContain("read_all");
|
|
176
|
+
});
|
|
177
|
+
(0, vitest_1.it)("POST /v3/database/coll with write_all but without database → database-keyword-missing", () => {
|
|
178
|
+
const d = (0, _scope_engine_1.evaluateScope)("write_all", "POST", "/v3/database/coll");
|
|
179
|
+
(0, vitest_1.expect)(d.allowed).toBe(false);
|
|
180
|
+
(0, vitest_1.expect)(d.rule).toBe("database-keyword-missing");
|
|
181
|
+
});
|
|
182
|
+
(0, vitest_1.it)("POST /v3/database/coll with write_all AND database → allowed", () => {
|
|
183
|
+
const d = (0, _scope_engine_1.evaluateScope)("write_all,database", "POST", "/v3/database/coll");
|
|
184
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
(0, vitest_1.it)("DELETE /v3/database/coll with delete_all AND database → allowed", () => {
|
|
187
|
+
const d = (0, _scope_engine_1.evaluateScope)("delete_all,database", "DELETE", "/v3/database/coll");
|
|
188
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
(0, vitest_1.it)("GET /v3/database/coll/aggregate denied when grammar itself denies (suggests entity token + database)", () => {
|
|
191
|
+
const d = (0, _scope_engine_1.evaluateScope)("database", "GET", "/v3/database/coll/aggregate");
|
|
192
|
+
(0, vitest_1.expect)(d.allowed).toBe(false);
|
|
193
|
+
(0, vitest_1.expect)(d.rule).toBe("denied");
|
|
194
|
+
(0, vitest_1.expect)(d.requiredTokens).toContain("database");
|
|
195
|
+
});
|
|
196
|
+
(0, vitest_1.it)("non-database path is unaffected by database keyword presence/absence", () => {
|
|
197
|
+
const d = (0, _scope_engine_1.evaluateScope)("read_all", "GET", "/v3/game");
|
|
198
|
+
(0, vitest_1.expect)(d.allowed).toBe(true);
|
|
199
|
+
(0, vitest_1.expect)(d.rule).not.toBe("database-keyword-missing");
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
// ─── T6: usedTokens ──────────────────────────────────────────────────────────
|
|
203
|
+
(0, vitest_1.describe)("usedTokens", () => {
|
|
204
|
+
(0, vitest_1.it)("returns empty set when no entries match", () => {
|
|
205
|
+
const used = (0, _scope_engine_1.usedTokens)("read_all", [{ method: "POST", path: "/v3/game" }]);
|
|
206
|
+
// POST needs write_* not read_*
|
|
207
|
+
(0, vitest_1.expect)(used.size).toBe(0);
|
|
208
|
+
});
|
|
209
|
+
(0, vitest_1.it)("attributes read_all when it authorizes a GET entry", () => {
|
|
210
|
+
const used = (0, _scope_engine_1.usedTokens)("read_all,write_all", [{ method: "GET", path: "/v3/game/level" }]);
|
|
211
|
+
(0, vitest_1.expect)(used.has("read_all")).toBe(true);
|
|
212
|
+
(0, vitest_1.expect)(used.has("write_all")).toBe(false);
|
|
213
|
+
});
|
|
214
|
+
(0, vitest_1.it)("attributes both grammar token and 'database' for /v3/database entries", () => {
|
|
215
|
+
const used = (0, _scope_engine_1.usedTokens)("read_all,database", [{ method: "GET", path: "/v3/database/coll" }]);
|
|
216
|
+
(0, vitest_1.expect)(used.has("read_all")).toBe(true);
|
|
217
|
+
(0, vitest_1.expect)(used.has("database")).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
(0, vitest_1.it)("does not attribute database when path is not /v3/database", () => {
|
|
220
|
+
const used = (0, _scope_engine_1.usedTokens)("read_all,database", [{ method: "GET", path: "/v3/game" }]);
|
|
221
|
+
(0, vitest_1.expect)(used.has("database")).toBe(false);
|
|
222
|
+
});
|
|
223
|
+
(0, vitest_1.it)("handles multiple entries across different tokens", () => {
|
|
224
|
+
const used = (0, _scope_engine_1.usedTokens)("read_all,write_game,database", [
|
|
225
|
+
{ method: "GET", path: "/v3/game" },
|
|
226
|
+
{ method: "POST", path: "/v3/game" },
|
|
227
|
+
]);
|
|
228
|
+
(0, vitest_1.expect)(used.has("read_all")).toBe(true);
|
|
229
|
+
(0, vitest_1.expect)(used.has("write_game")).toBe(true);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
// ─── MANUAL_REVIEW_TOKENS ─────────────────────────────────────────────────────
|
|
233
|
+
(0, vitest_1.describe)("MANUAL_REVIEW_TOKENS", () => {
|
|
234
|
+
(0, vitest_1.it)("includes cross_domain, read_encrypted_field_values, read_encrypted_player_password, write_upload", () => {
|
|
235
|
+
(0, vitest_1.expect)(_scope_engine_1.MANUAL_REVIEW_TOKENS.has("cross_domain")).toBe(true);
|
|
236
|
+
(0, vitest_1.expect)(_scope_engine_1.MANUAL_REVIEW_TOKENS.has("read_encrypted_field_values")).toBe(true);
|
|
237
|
+
(0, vitest_1.expect)(_scope_engine_1.MANUAL_REVIEW_TOKENS.has("read_encrypted_player_password")).toBe(true);
|
|
238
|
+
(0, vitest_1.expect)(_scope_engine_1.MANUAL_REVIEW_TOKENS.has("write_upload")).toBe(true);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
//# sourceMappingURL=_scope-engine.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_scope-engine.test.js","sourceRoot":"","sources":["../../../src/mcp/tools/_scope-engine.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/permissions.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/permissions.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAmW1C,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,QAkT9E"}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.registerPermissionsTool = registerPermissionsTool;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const _backup_1 = require("./_backup");
|
|
6
|
+
const _audit_1 = require("./_audit");
|
|
6
7
|
function parseJsonObject(value, label) {
|
|
7
8
|
if (!value) {
|
|
8
9
|
throw new Error(`'${label}' is required as a JSON object.`);
|
|
@@ -274,7 +275,8 @@ function registerPermissionsTool(server, apiHolder) {
|
|
|
274
275
|
"Studio/UI permissions are stored in system roles (role collection) and assignments (principal_role). " +
|
|
275
276
|
"Use API actions for app_secret/scope/whitelist management, and Studio actions for page/api/limit permissions and role assignments. " +
|
|
276
277
|
"Every mutating action writes a complete pre-image snapshot under .funifier/backups/ (or FUNIFIER_BACKUP_ROOT) BEFORE the remote write; " +
|
|
277
|
-
"use list_backups to see them
|
|
278
|
+
"use list_backups to see them, restore_backup (with backup_path) to roll a change back, and verify_backup (with backup_path) to check any snapshot is restorable without side effects. " +
|
|
279
|
+
"Use audit (with a JSON manifest in 'data') to cross-check your project's Funifier API calls against the live security document — returns missing scopes, excess tokens, and static danger findings without any mutation or snapshot. " +
|
|
278
280
|
"Snapshots accumulate without automatic retention — prune .funifier/backups/ manually when needed.",
|
|
279
281
|
inputSchema: {
|
|
280
282
|
action: zod_1.z
|
|
@@ -293,6 +295,8 @@ function registerPermissionsTool(server, apiHolder) {
|
|
|
293
295
|
"unassign_studio_role",
|
|
294
296
|
"list_backups",
|
|
295
297
|
"restore_backup",
|
|
298
|
+
"verify_backup",
|
|
299
|
+
"audit",
|
|
296
300
|
])
|
|
297
301
|
.describe("Permission operation to execute."),
|
|
298
302
|
data: zod_1.z
|
|
@@ -301,7 +305,8 @@ function registerPermissionsTool(server, apiHolder) {
|
|
|
301
305
|
.describe("JSON object payload. For save_api_app: {name, app_secret, scope, whitelist?}. " +
|
|
302
306
|
"For save_security_role: {name, timeout, scope, whitelist?}. " +
|
|
303
307
|
"For save_studio_role: {_id?, name, type, item, permissions:[{type, object, operations}], gamification_whitelist?, gamification_blacklist?}. " +
|
|
304
|
-
"For assign/unassign_studio_role: {type:'user|account|gamification', item:'<principal id>', role:'<role id>'}."
|
|
308
|
+
"For assign/unassign_studio_role: {type:'user|account|gamification', item:'<principal id>', role:'<role id>'}. " +
|
|
309
|
+
"For audit: {version:1, entries:[{method, path, auth, evidence, confidence?}]} where auth is 'public'|'player'|'role:<name>'|'app:<name>'."),
|
|
305
310
|
app_secret: zod_1.z.string().optional().describe("Application secret used by delete_api_app."),
|
|
306
311
|
name: zod_1.z.string().optional().describe("Security role name used by delete_security_role."),
|
|
307
312
|
role_id: zod_1.z.string().optional().describe("Studio role _id used by delete_studio_role or assignment helpers."),
|
|
@@ -325,6 +330,38 @@ function registerPermissionsTool(server, apiHolder) {
|
|
|
325
330
|
},
|
|
326
331
|
}, async ({ action, data, app_secret, name, role_id, role_type, item, principal_type, principal_item, backup_path }) => {
|
|
327
332
|
try {
|
|
333
|
+
// verify_backup works without a connection — handle before requireClient()
|
|
334
|
+
if (action === "verify_backup") {
|
|
335
|
+
if (!backup_path)
|
|
336
|
+
throw new Error("'backup_path' is required for verify_backup.");
|
|
337
|
+
const resolvedPath = (0, _backup_1.resolveBackupPath)(backup_path);
|
|
338
|
+
const checks = [];
|
|
339
|
+
const readResult = (0, _backup_1.safeReadSnapshot)(resolvedPath);
|
|
340
|
+
if (!readResult.ok) {
|
|
341
|
+
checks.push({ check: readResult.check, ok: false, detail: readResult.detail });
|
|
342
|
+
return formatResult(`verify_backup: FAIL — ${backup_path}`, { verdict: "FAIL", backup_path, checks });
|
|
343
|
+
}
|
|
344
|
+
checks.push({ check: "file-readable", ok: true });
|
|
345
|
+
checks.push({ check: "snapshot-schema", ok: true });
|
|
346
|
+
const vbSnapshot = readResult.snapshot;
|
|
347
|
+
checks.push(...(0, _backup_1.validateSnapshotRestorable)(vbSnapshot));
|
|
348
|
+
// Server-match check: non-throwing wrap, skipped when disconnected (PSEC-05)
|
|
349
|
+
const connInfo = apiHolder.getConnectionInfo();
|
|
350
|
+
if (connInfo.connected) {
|
|
351
|
+
try {
|
|
352
|
+
assertSnapshotServerMatches(vbSnapshot, connInfo.serverUrl);
|
|
353
|
+
checks.push({ check: "server-url-match", ok: true });
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
checks.push({ check: "server-url-match", ok: false, detail: err.message });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
checks.push({ check: "server-url-match", ok: true, detail: "Skipped — not connected to any server." });
|
|
361
|
+
}
|
|
362
|
+
const verdict = checks.every((c) => c.ok) ? "PASS" : "FAIL";
|
|
363
|
+
return formatResult(`verify_backup: ${verdict} — ${backup_path}`, { verdict, backup_path, checks });
|
|
364
|
+
}
|
|
328
365
|
const api = apiHolder.requireClient();
|
|
329
366
|
const serverUrl = apiHolder.getConnectionInfo?.().serverUrl ?? null;
|
|
330
367
|
if (action === "list_api_apps") {
|
|
@@ -428,20 +465,40 @@ function registerPermissionsTool(server, apiHolder) {
|
|
|
428
465
|
const snapshots = (0, _backup_1.listSnapshots)();
|
|
429
466
|
return formatResult(`Backups (${snapshots.length})`, snapshots);
|
|
430
467
|
}
|
|
468
|
+
if (action === "audit") {
|
|
469
|
+
if (!data)
|
|
470
|
+
throw new Error("'data' is required for audit — provide a JSON manifest with version:1 and entries.");
|
|
471
|
+
let rawManifest;
|
|
472
|
+
try {
|
|
473
|
+
rawManifest = JSON.parse(data);
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
throw new Error("'data' is not valid JSON.");
|
|
477
|
+
}
|
|
478
|
+
const parseResult = _audit_1.auditManifestSchema.safeParse(rawManifest);
|
|
479
|
+
if (!parseResult.success) {
|
|
480
|
+
const first = parseResult.error.issues[0];
|
|
481
|
+
const entryIdx = typeof first.path[1] === "number" ? ` (entry ${first.path[1]})` : "";
|
|
482
|
+
throw new Error(`Manifest validation failed${entryIdx}: ${first.message}`);
|
|
483
|
+
}
|
|
484
|
+
const manifest = parseResult.data;
|
|
485
|
+
// Load security document read-only — abort loudly on any failure (never partial verdict)
|
|
486
|
+
const securityDoc = await loadSecurityDocument(api);
|
|
487
|
+
// Mask app_secret values before passing to the audit engine
|
|
488
|
+
const maskedDoc = {
|
|
489
|
+
...securityDoc,
|
|
490
|
+
apps: maskSecurityApplications(securityDoc.apps),
|
|
491
|
+
};
|
|
492
|
+
const report = (0, _audit_1.runAudit)(manifest, maskedDoc);
|
|
493
|
+
return formatResult("audit", report);
|
|
494
|
+
}
|
|
431
495
|
if (action === "restore_backup") {
|
|
432
496
|
if (!backup_path)
|
|
433
497
|
throw new Error("'backup_path' is required for restore_backup.");
|
|
434
498
|
// Read + validate first: a missing/corrupt/invalid file throws before any remote write.
|
|
435
499
|
const snapshot = (0, _backup_1.readSnapshot)((0, _backup_1.resolveBackupPath)(backup_path));
|
|
436
|
-
//
|
|
437
|
-
|
|
438
|
-
if (snapshot.category === "security") {
|
|
439
|
-
const pre = snapshot.preImage;
|
|
440
|
-
if (!pre || pre._id == null || !Array.isArray(pre.apps) || !Array.isArray(pre.roles)) {
|
|
441
|
-
throw new Error("Security snapshot pre-image is malformed: missing '_id', or 'apps'/'roles' are not arrays. " +
|
|
442
|
-
"Refusing to apply a snapshot that could truncate the document.");
|
|
443
|
-
}
|
|
444
|
-
}
|
|
500
|
+
// Shared validator: same checks as capture-time dry-run — single source of truth (PSEC-04).
|
|
501
|
+
(0, _backup_1.assertSnapshotRestorable)(snapshot);
|
|
445
502
|
assertSnapshotServerMatches(snapshot, serverUrl);
|
|
446
503
|
// Restore is itself a critical mutation, so it takes its own pre-write backup of current state.
|
|
447
504
|
// Critical #1: skipContentCheck — the live document may already be truncated (that is why we restore).
|