drupal-mcp-connector 0.9.0 → 0.9.1
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/CHANGELOG.md +17 -0
- package/README.md +1 -0
- package/package.json +1 -1
- package/src/lib/backends/jsonapi.js +13 -4
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.9.1] - 2026-06-15
|
|
11
|
+
|
|
12
|
+
### Security
|
|
13
|
+
- Validate and URL-encode JSON:API path segments. The entity `id` is now checked
|
|
14
|
+
with `validateUuid` and `entityType`/`bundle` with `validateMachineName` (both
|
|
15
|
+
previously unused), and every segment is `encodeURIComponent`'d. This closes a
|
|
16
|
+
path-traversal vector where a crafted `id` (e.g. `../../user/user/<uuid>`) could
|
|
17
|
+
reach a different resource type and bypass the connector's entity-type/PII
|
|
18
|
+
policy. (Drupal core permissions were always still enforced.)
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- A [Threat Model](docs/threat-model.md) documenting trust boundaries, threats &
|
|
22
|
+
mitigations, residual risks (drush SQL bridge; why filter-field names are not
|
|
23
|
+
machine-name-validated), and the 1.0 security-pass results (`npm audit` clean,
|
|
24
|
+
adversarial review).
|
|
25
|
+
|
|
10
26
|
## [0.9.0] - 2026-06-15
|
|
11
27
|
|
|
12
28
|
### Added
|
|
@@ -213,6 +229,7 @@ The connector is now **dual-protocol**: every tool runs against an abstract back
|
|
|
213
229
|
- User tools gained explicit PII-access assertions.
|
|
214
230
|
- Whole tree lint-clean (`npm run lint`) with object-injection sinks rewritten to safe lookups.
|
|
215
231
|
|
|
232
|
+
[0.9.1]: https://github.com/Wilkes-Liberty/drupal-mcp-connector/releases/tag/v0.9.1
|
|
216
233
|
[0.9.0]: https://github.com/Wilkes-Liberty/drupal-mcp-connector/releases/tag/v0.9.0
|
|
217
234
|
[0.8.0]: https://github.com/Wilkes-Liberty/drupal-mcp-connector/releases/tag/v0.8.0
|
|
218
235
|
[0.7.1]: https://github.com/Wilkes-Liberty/drupal-mcp-connector/releases/tag/v0.7.1
|
package/README.md
CHANGED
|
@@ -177,6 +177,7 @@ Governance keys off the authenticated account's role and OAuth scopes — not re
|
|
|
177
177
|
| [Tools Reference](docs/tools-reference.md) | Full reference for all 66 tools |
|
|
178
178
|
| [Security Guide](docs/security.md) | Presets, entity access control, field redaction |
|
|
179
179
|
| [Security Hardening](docs/security-hardening.md) | Optional transport, identity, and secrets controls |
|
|
180
|
+
| [Threat Model](docs/threat-model.md) | Trust boundaries, threats & mitigations, residual risks, and the security-pass results |
|
|
180
181
|
| [Deployment](docs/deployment.md) | Run the HTTPS transport in production: Docker, systemd, launchd, reverse proxy, pre-exposure checklist |
|
|
181
182
|
| [Integration Contract](docs/integration-contract.md) | The connector ↔ Drupal-governance contract (identity, OAuth scopes, compatibility) |
|
|
182
183
|
| [Versioning & Stability](docs/versioning.md) | Semver policy: the stable surface, deprecation process, MCP protocol + Node support |
|
package/package.json
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { drupalFetch, drupalUploadFile } from "../drupal-fetch.js";
|
|
11
|
+
import { validateUuid, validateMachineName } from "../validate.js";
|
|
11
12
|
import { Backend } from "./backend-interface.js";
|
|
12
13
|
import {
|
|
13
14
|
makeCanonicalEntity,
|
|
@@ -132,7 +133,12 @@ export class JsonApiBackend extends Backend {
|
|
|
132
133
|
* @returns {string} e.g. "/jsonapi/node/article".
|
|
133
134
|
*/
|
|
134
135
|
resourcePath(entityType, bundle) {
|
|
135
|
-
|
|
136
|
+
// Validate before interpolating into the URL: machine names cannot contain
|
|
137
|
+
// path separators or `..`, so this blocks path-traversal to other resources
|
|
138
|
+
// (e.g. id="../../user/user/…"). Encoding is belt-and-suspenders.
|
|
139
|
+
validateMachineName(entityType, "entityType");
|
|
140
|
+
validateMachineName(bundle, "bundle");
|
|
141
|
+
return `/jsonapi/${encodeURIComponent(entityType)}/${encodeURIComponent(bundle)}`;
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
/**
|
|
@@ -219,7 +225,8 @@ export class JsonApiBackend extends Backend {
|
|
|
219
225
|
* @returns {Promise<?import("../canonical.js").CanonicalEntity>} Entity, or null.
|
|
220
226
|
*/
|
|
221
227
|
async getEntity({ entityType, bundle, id }) {
|
|
222
|
-
|
|
228
|
+
validateUuid(id);
|
|
229
|
+
const data = await drupalFetch(this.site, `${this.resourcePath(entityType, bundle)}/${encodeURIComponent(id)}`);
|
|
223
230
|
return data?.data ? this.toCanonical(data.data) : null;
|
|
224
231
|
}
|
|
225
232
|
|
|
@@ -275,12 +282,13 @@ export class JsonApiBackend extends Backend {
|
|
|
275
282
|
* @returns {Promise<import("../canonical.js").CanonicalEntity>} The updated entity.
|
|
276
283
|
*/
|
|
277
284
|
async updateEntity({ entityType, bundle, id, attributes = {}, relationships }) {
|
|
285
|
+
validateUuid(id);
|
|
278
286
|
const buildPayload = (attrs) => {
|
|
279
287
|
const payload = { data: { type: `${entityType}--${bundle}`, id, attributes: attrs } };
|
|
280
288
|
if (relationships) payload.data.relationships = relationships;
|
|
281
289
|
return payload;
|
|
282
290
|
};
|
|
283
|
-
const data = await this.writeWithModerationFallback(`${this.resourcePath(entityType, bundle)}/${id}`, "PATCH", buildPayload, attributes);
|
|
291
|
+
const data = await this.writeWithModerationFallback(`${this.resourcePath(entityType, bundle)}/${encodeURIComponent(id)}`, "PATCH", buildPayload, attributes);
|
|
284
292
|
return this.toCanonical(data.data);
|
|
285
293
|
}
|
|
286
294
|
|
|
@@ -290,7 +298,8 @@ export class JsonApiBackend extends Backend {
|
|
|
290
298
|
* @returns {Promise<void>}
|
|
291
299
|
*/
|
|
292
300
|
async deleteEntity({ entityType, bundle, id }) {
|
|
293
|
-
|
|
301
|
+
validateUuid(id);
|
|
302
|
+
await drupalFetch(this.site, `${this.resourcePath(entityType, bundle)}/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
294
303
|
}
|
|
295
304
|
|
|
296
305
|
/**
|