Skip to content

Commit 6fbbbbe

Browse files
committed
prevent plugins to access unwanted or sensative platform data #104
1 parent e6f78d9 commit 6fbbbbe

File tree

2 files changed

+70
-1
lines changed

2 files changed

+70
-1
lines changed

platform/src/config/env.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ const baseTemplate = Object.freeze({
125125
TIMEZONE_DEFAULT: process.env.DEFAULT_TIMEZONE || "UTC",
126126
CURRENCY_DEFAULT: process.env.DEFAULT_CURRENCY || "EUR",
127127
DEFAULT_TENANT_ID: manifest.defaultTenantId || "tenant-0",
128+
SENSITIVE_PLUGIN_ALLOWLIST: splitCsv(process.env.SENSITIVE_PLUGIN_ALLOWLIST || "users,settings"),
128129

129130
NODE_ENV: process.env.NODE_ENV || "development",
130131
PORT: Number(process.env.PORT) || 3000,

platform/src/ext-host/capabilities.js

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,83 @@ import * as mailer from "$/services/mailer.js";
55
import { uuid } from "$/utils/id.js";
66
import { refreshEnvCache } from "$/config/env.js";
77

8+
import { PluginCapabilityError } from "./plugin-auth.js";
9+
810
export const DEV_ALLOW_ALL_CAPS = process.env.DEV_ALLOW_ALL_CAPS === "true";
911

12+
const SENSITIVE_MODELS = new Set([
13+
"User",
14+
"UserProfile",
15+
"UserEmail",
16+
"UserRole",
17+
"UserRoleAssignment",
18+
"UserRoleCapability",
19+
"UserCapability",
20+
"Session",
21+
"PasskeyCredential",
22+
"PasskeyChallenge",
23+
"VerificationToken",
24+
"PasswordResetToken",
25+
"Invite",
26+
"AuditLog",
27+
"Tenant",
28+
]);
29+
30+
function normalizeNamespace(value) {
31+
return String(value || "")
32+
.replace(/^@[^/]+\/+/, "")
33+
.trim()
34+
.toLowerCase();
35+
}
36+
37+
function isWhitelistedForSensitiveModels(plugin, config = {}) {
38+
const configured = Array.isArray(config.SENSITIVE_PLUGIN_ALLOWLIST)
39+
? config.SENSITIVE_PLUGIN_ALLOWLIST
40+
: [];
41+
const allowlist = new Set(configured.map((item) => normalizeNamespace(item)));
42+
43+
const ns = normalizeNamespace(plugin?.namespace);
44+
const id = normalizeNamespace(plugin?.id);
45+
return allowlist.has(ns) || allowlist.has(id);
46+
}
47+
48+
function getPrismaForPlugin(plugin, config = {}) {
49+
const namespace = (plugin?.namespace || plugin?.id || "<unknown>").toString();
50+
51+
const isWhitelistedCore =
52+
plugin?.corePlugin === true && isWhitelistedForSensitiveModels(plugin, config);
53+
54+
if (isWhitelistedCore) return prisma;
55+
56+
return prisma.$extends({
57+
name: `plugin-guard:${namespace}`,
58+
query: {
59+
$allModels: {
60+
async $allOperations({ model }, next) {
61+
if (SENSITIVE_MODELS.has(model)) {
62+
throw new PluginCapabilityError(
63+
`Plugin "${namespace}" is not allowed to access model "${model}"`,
64+
{
65+
status: 403,
66+
code: "ERR_PLUGIN_DATA_ACCESS",
67+
meta: { namespace, model },
68+
}
69+
);
70+
}
71+
return next();
72+
},
73+
},
74+
},
75+
});
76+
}
77+
1078
const capabilityRegistry = {
1179
database: {
1280
key: "database",
1381
provides: "prisma",
1482
description: "Read/write access to the primary database via Prisma client",
1583
risk: "critical",
16-
resolve: () => prisma,
84+
resolve: ({ plugin, config }) => getPrismaForPlugin(plugin, config),
1785
},
1886
git: {
1987
key: "git",

0 commit comments

Comments
 (0)