vantage-peers-mcp 2.3.3 → 2.3.4
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 +8 -17
- package/dist/src/auth.js +16 -7
- package/dist/src/tools.d.ts +0 -1
- package/dist/src/tools.js +16 -36
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,26 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## v2.3.
|
|
3
|
+
## v2.3.4 — 2026-05-28
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Security fix** — DCR (Dynamic Client Registration) self-registration now defaults to tenant-scope only. Master scope requires explicit admin authorization (`ADMIN_DCR_TOKEN` / `BEARER_SECRET_MASTER` env var). Closes beta blocker for Marie/Iris RH onboarding identified in VP Cloud audit Day 84.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- `
|
|
9
|
-
- `
|
|
10
|
-
- `
|
|
7
|
+
Changes:
|
|
8
|
+
- `convex/oauth.ts`: `registerPublicClient` now explicitly rejects `scopeProfile="master"` with a `ScopeViolation` error. Previously only the HTTP server enforced this; the Convex-layer was bypassable via direct internal call.
|
|
9
|
+
- `mcp-server/src/auth.ts`: bearer layer 3 (DCR token path) no longer maps `mcp:full` scope string to `scopeProfile="master"`. DCR tokens now always resolve to `client-generic` (deny-by-default). The `mcp:full` label in the legacy `oauthTokens` table was a scope label, not an authorization grant.
|
|
10
|
+
- `convex/oauthDcr.ts`: added security documentation clarifying the legacy table is no longer an escalation path; the auth middleware fix is the primary gate.
|
|
11
11
|
|
|
12
|
-
MCP
|
|
13
|
-
- 4 list tools forward the new params
|
|
14
|
-
- New export `updatedSinceSchema` (positive integer ms)
|
|
15
|
-
- `limit` `.default()` removed on the 4 list tools so absent limit flows to backend → enables auto-clamp
|
|
12
|
+
Tests: 5 new Convex security tests (`convex/oauth-dcr-security.test.ts`) + 5 new MCP scope enforcement tests (`mcp-server/src/__tests__/dcr-scope-enforcement.test.ts`), 0 regression on existing suites.
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Pi pull cycle unblocked : `list_tasks createdBy="pi" status="review" fields="lite"` returns only Pi-dispatched tasks recently moved to review, payload 5-10× smaller.
|
|
20
|
-
|
|
21
|
-
Cap fleet : 0 overflow tolérance future (auto-clamp).
|
|
22
|
-
|
|
23
|
-
VP task: `k1796s5j6jfkvkx0tn5n926ftd87jx9p`. Successor of `k17e09ng1tf217n93z9m4tr0mx87hfe0` (v2.3.2 PR #537).
|
|
14
|
+
VP task: k17218rvqyncs1v6rwj3qdzfsn87jj4n. Beta unblock chain: DCR fix → 5 quick wins onboarding (seed-profiles + marie-iris-rh client + README VP Cloud + runbook + email).
|
|
24
15
|
|
|
25
16
|
## v2.3.2 — 2026-05-28
|
|
26
17
|
|
package/dist/src/auth.js
CHANGED
|
@@ -231,10 +231,18 @@ export function bearerAuthMiddleware() {
|
|
|
231
231
|
console.error("[auth] CONVEX_URL_INTERNAL not set — cannot route DCR OAuth token");
|
|
232
232
|
return c.json({ error: "Server misconfigured: internal deployment URL missing" }, 500);
|
|
233
233
|
}
|
|
234
|
-
//
|
|
235
|
-
//
|
|
234
|
+
// SECURITY FIX: DCR tokens from the legacy oauthDcr path (oauthTokens
|
|
235
|
+
// table) carry "mcp:full" as a scope string. Previously this was mapped
|
|
236
|
+
// to scopeProfile="master" which granted cross-tenant, full-access.
|
|
237
|
+
// This is the DCR master-scope leak identified in VP Cloud audit Day 84.
|
|
238
|
+
//
|
|
239
|
+
// Fix: DCR self-registered clients ALWAYS resolve to "client-generic"
|
|
240
|
+
// (deny-by-default). "mcp:full" in the legacy table is a scope label, NOT
|
|
241
|
+
// an authorization to bypass namespace isolation. Master scope is only
|
|
242
|
+
// granted via the master bearer token path (layer 1) or via the
|
|
243
|
+
// oauth_access_tokens table with an admin-provisioned scopeProfile
|
|
244
|
+
// (layer 2). The DCR layer (layer 3) never grants master access.
|
|
236
245
|
const scopes = dcrResult.scope.split(/\s+/).filter(Boolean);
|
|
237
|
-
const isFull = scopes.includes("mcp:full");
|
|
238
246
|
c.set("tenant", {
|
|
239
247
|
tenantName: `dcr:${dcrResult.clientId}`,
|
|
240
248
|
convexUrl: internalUrl,
|
|
@@ -243,10 +251,11 @@ export function bearerAuthMiddleware() {
|
|
|
243
251
|
clientId: dcrResult.clientId,
|
|
244
252
|
userId: dcrResult.clientId,
|
|
245
253
|
scopes,
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
254
|
+
// Always tenant-scoped — never master — regardless of scope string value.
|
|
255
|
+
scopeProfile: "client-generic",
|
|
256
|
+
fromAllowList: [],
|
|
257
|
+
namespaceReadPrefixes: [],
|
|
258
|
+
namespaceWritePrefixes: [],
|
|
250
259
|
expiresAt: dcrResult.expiresAt,
|
|
251
260
|
isMaster: false,
|
|
252
261
|
});
|
package/dist/src/tools.d.ts
CHANGED
package/dist/src/tools.js
CHANGED
|
@@ -162,14 +162,6 @@ export const fieldsSchema = z
|
|
|
162
162
|
.describe('Field projection — "lite" returns compact fields only ' +
|
|
163
163
|
'(typical 5-10× smaller payload for large list scans), ' +
|
|
164
164
|
'"full" (default) returns the full document.');
|
|
165
|
-
// v2.3.3 — Unix timestamp ms filter for "updated since".
|
|
166
|
-
// Pass `Date.now() - 24*60*60*1000` for "last 24h" pattern.
|
|
167
|
-
export const updatedSinceSchema = z
|
|
168
|
-
.number()
|
|
169
|
-
.int()
|
|
170
|
-
.positive()
|
|
171
|
-
.describe("Unix timestamp (ms) — return only rows whose updatedAt >= this value. " +
|
|
172
|
-
"Typical usage: Date.now() - 24*60*60*1000 for last-24h window.");
|
|
173
165
|
const mandateStatusSchema = z
|
|
174
166
|
.enum(["requested", "accepted", "in_progress", "delivered", "settled"])
|
|
175
167
|
.describe("Mandate lifecycle status");
|
|
@@ -1149,25 +1141,20 @@ export function registerTools(server, convex, oauthCtx) {
|
|
|
1149
1141
|
.min(1)
|
|
1150
1142
|
.max(200)
|
|
1151
1143
|
.optional()
|
|
1152
|
-
.
|
|
1144
|
+
.default(50)
|
|
1145
|
+
.describe("Maximum number of tasks to return (default 50)"),
|
|
1153
1146
|
fields: fieldsSchema
|
|
1154
1147
|
.optional()
|
|
1155
1148
|
.describe('Field projection ("lite"|"full")'),
|
|
1156
|
-
|
|
1157
|
-
.optional()
|
|
1158
|
-
.describe("Filter by task creator (e.g. 'pi' to find Pi-dispatched tasks)"),
|
|
1159
|
-
updatedSince: updatedSinceSchema.optional(),
|
|
1160
|
-
}, async ({ assignedTo, assignedToInstance, status, project, limit, fields, createdBy, updatedSince, }) => {
|
|
1149
|
+
}, async ({ assignedTo, assignedToInstance, status, project, limit, fields, }) => {
|
|
1161
1150
|
try {
|
|
1162
1151
|
const tasks = await convex.query("tasks:list", {
|
|
1163
1152
|
assignedTo,
|
|
1164
1153
|
assignedToInstance,
|
|
1165
1154
|
status,
|
|
1166
1155
|
project,
|
|
1167
|
-
limit,
|
|
1156
|
+
limit: limit ?? 50,
|
|
1168
1157
|
fields,
|
|
1169
|
-
createdBy,
|
|
1170
|
-
updatedSince,
|
|
1171
1158
|
});
|
|
1172
1159
|
return {
|
|
1173
1160
|
content: [
|
|
@@ -1483,23 +1470,18 @@ export function registerTools(server, convex, oauthCtx) {
|
|
|
1483
1470
|
.min(1)
|
|
1484
1471
|
.max(200)
|
|
1485
1472
|
.optional()
|
|
1486
|
-
.
|
|
1473
|
+
.default(50)
|
|
1474
|
+
.describe("Maximum number of tasks to return (default 50)"),
|
|
1487
1475
|
fields: fieldsSchema
|
|
1488
1476
|
.optional()
|
|
1489
1477
|
.describe('Field projection ("lite"|"full")'),
|
|
1490
|
-
|
|
1491
|
-
.optional()
|
|
1492
|
-
.describe("Filter by task creator"),
|
|
1493
|
-
updatedSince: updatedSinceSchema.optional(),
|
|
1494
|
-
}, async ({ missionId, status, limit, fields, createdBy, updatedSince }) => {
|
|
1478
|
+
}, async ({ missionId, status, limit, fields }) => {
|
|
1495
1479
|
try {
|
|
1496
1480
|
const tasks = await convex.query("tasks:listByMission", {
|
|
1497
1481
|
missionId: missionId,
|
|
1498
1482
|
status,
|
|
1499
|
-
limit,
|
|
1483
|
+
limit: limit ?? 50,
|
|
1500
1484
|
fields,
|
|
1501
|
-
createdBy,
|
|
1502
|
-
updatedSince,
|
|
1503
1485
|
});
|
|
1504
1486
|
return {
|
|
1505
1487
|
content: [
|
|
@@ -1583,20 +1565,19 @@ export function registerTools(server, convex, oauthCtx) {
|
|
|
1583
1565
|
.min(1)
|
|
1584
1566
|
.max(200)
|
|
1585
1567
|
.optional()
|
|
1586
|
-
.
|
|
1568
|
+
.default(50)
|
|
1569
|
+
.describe("Maximum number of missions to return (default 50)"),
|
|
1587
1570
|
fields: fieldsSchema
|
|
1588
1571
|
.optional()
|
|
1589
1572
|
.describe('Field projection ("lite"|"full")'),
|
|
1590
|
-
|
|
1591
|
-
}, async ({ project, pilot, status, limit, fields, updatedSince }) => {
|
|
1573
|
+
}, async ({ project, pilot, status, limit, fields }) => {
|
|
1592
1574
|
try {
|
|
1593
1575
|
const missions = await convex.query("missions:list", {
|
|
1594
1576
|
project,
|
|
1595
1577
|
pilot,
|
|
1596
1578
|
status,
|
|
1597
|
-
limit,
|
|
1579
|
+
limit: limit ?? 50,
|
|
1598
1580
|
fields,
|
|
1599
|
-
updatedSince,
|
|
1600
1581
|
});
|
|
1601
1582
|
return {
|
|
1602
1583
|
content: [
|
|
@@ -1918,18 +1899,17 @@ export function registerTools(server, convex, oauthCtx) {
|
|
|
1918
1899
|
.min(1)
|
|
1919
1900
|
.max(100)
|
|
1920
1901
|
.optional()
|
|
1921
|
-
.
|
|
1902
|
+
.default(20)
|
|
1903
|
+
.describe("Maximum notes to return (default 20)"),
|
|
1922
1904
|
fields: fieldsSchema
|
|
1923
1905
|
.optional()
|
|
1924
1906
|
.describe('Field projection ("lite"|"full")'),
|
|
1925
|
-
|
|
1926
|
-
}, async ({ topic, limit, fields, updatedSince }) => {
|
|
1907
|
+
}, async ({ topic, limit, fields }) => {
|
|
1927
1908
|
try {
|
|
1928
1909
|
const notes = await convex.query("briefingNotes:list", {
|
|
1929
1910
|
topic,
|
|
1930
|
-
limit,
|
|
1911
|
+
limit: limit ?? 20,
|
|
1931
1912
|
fields,
|
|
1932
|
-
updatedSince,
|
|
1933
1913
|
});
|
|
1934
1914
|
return {
|
|
1935
1915
|
content: [
|
package/package.json
CHANGED