@systemdox/mcp-server 0.1.55 → 0.1.57

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.
@@ -59,6 +59,17 @@ var SystemDoxApiError = class extends Error {
59
59
  code;
60
60
  body;
61
61
  };
62
+ function unescapedPath(path) {
63
+ let current = path.split(/[?#]/, 1)[0];
64
+ for (; ; ) {
65
+ const next = current.replace(/[\t\n\r]/g, "").replace(/%(?:2e|2f|5c|25|09|0a|0d)/gi, (escape) => decodeURIComponent(escape));
66
+ if (next === current) return current;
67
+ current = next;
68
+ }
69
+ }
70
+ function walksThePath(path) {
71
+ return unescapedPath(path).split(/[/\\]/).some((segment) => segment === "." || segment === "..");
72
+ }
62
73
  var TEAM_SETUP = "/v1/team-setup";
63
74
  var seg = (s) => encodeURIComponent(String(s));
64
75
  var SystemDoxApiClient = class {
@@ -94,7 +105,7 @@ var SystemDoxApiClient = class {
94
105
  }
95
106
  async mutate(method, path, body) {
96
107
  const url = this.buildUrl(path);
97
- const res = await fetch(url, {
108
+ const res = await this.call(url, {
98
109
  method,
99
110
  headers: {
100
111
  // The API Gateway authorizer only reads the Authorization header;
@@ -144,7 +155,7 @@ var SystemDoxApiClient = class {
144
155
  /** getPluginZip: the zip's bytes as served, and its X-Content-SHA256
145
156
  * header. The caller checks the BYTES against the hash it was given. */
146
157
  async getPluginZip(workspace, version) {
147
- const res = await fetch(
158
+ const res = await this.call(
148
159
  this.buildUrl(`${TEAM_SETUP}/${seg(workspace)}/plugin/versions/${seg(version)}/zip`),
149
160
  {
150
161
  headers: {
@@ -210,6 +221,10 @@ var SystemDoxApiClient = class {
210
221
  buildUrl(path, params) {
211
222
  const base = this.config.apiUrl.replace(/\/+$/, "");
212
223
  const url = new URL(`${base}${path.startsWith("/") ? "" : "/"}${path}`);
224
+ const { origin, pathname } = new URL(base);
225
+ if (walksThePath(path) || url.origin !== origin || !url.pathname.startsWith(`${pathname.replace(/\/+$/, "")}/`)) {
226
+ throw new Error("Refused: this id leaves the API's own path.");
227
+ }
213
228
  if (params) {
214
229
  for (const [key, value] of Object.entries(params)) {
215
230
  url.searchParams.set(key, value);
@@ -219,7 +234,7 @@ var SystemDoxApiClient = class {
219
234
  }
220
235
  async fetchRaw(path, params, signal) {
221
236
  const url = this.buildUrl(path, params);
222
- return fetch(url, {
237
+ return this.call(url, {
223
238
  headers: {
224
239
  Authorization: `Bearer ${this.config.apiKey}`,
225
240
  "X-API-Key": this.config.apiKey,
@@ -228,6 +243,16 @@ var SystemDoxApiClient = class {
228
243
  signal
229
244
  });
230
245
  }
246
+ /** Every call of this client, so `onAnswer` hears each answer. */
247
+ async call(url, init) {
248
+ const res = await fetch(url, init);
249
+ const { onAnswer } = this.config;
250
+ if (onAnswer) {
251
+ const refused = res.ok ? void 0 : new SystemDoxApiError(res.status, await res.clone().text());
252
+ onAnswer({ status: res.status, code: refused?.code });
253
+ }
254
+ return res;
255
+ }
231
256
  };
232
257
 
233
258
  // src/lib/memory-sync.ts
@@ -56,6 +56,17 @@ var SystemDoxApiError = class extends Error {
56
56
  code;
57
57
  body;
58
58
  };
59
+ function unescapedPath(path) {
60
+ let current = path.split(/[?#]/, 1)[0];
61
+ for (; ; ) {
62
+ const next = current.replace(/[\t\n\r]/g, "").replace(/%(?:2e|2f|5c|25|09|0a|0d)/gi, (escape) => decodeURIComponent(escape));
63
+ if (next === current) return current;
64
+ current = next;
65
+ }
66
+ }
67
+ function walksThePath(path) {
68
+ return unescapedPath(path).split(/[/\\]/).some((segment) => segment === "." || segment === "..");
69
+ }
59
70
  var TEAM_SETUP = "/v1/team-setup";
60
71
  var seg = (s) => encodeURIComponent(String(s));
61
72
  var SystemDoxApiClient = class {
@@ -91,7 +102,7 @@ var SystemDoxApiClient = class {
91
102
  }
92
103
  async mutate(method, path, body) {
93
104
  const url = this.buildUrl(path);
94
- const res = await fetch(url, {
105
+ const res = await this.call(url, {
95
106
  method,
96
107
  headers: {
97
108
  // The API Gateway authorizer only reads the Authorization header;
@@ -141,7 +152,7 @@ var SystemDoxApiClient = class {
141
152
  /** getPluginZip: the zip's bytes as served, and its X-Content-SHA256
142
153
  * header. The caller checks the BYTES against the hash it was given. */
143
154
  async getPluginZip(workspace, version) {
144
- const res = await fetch(
155
+ const res = await this.call(
145
156
  this.buildUrl(`${TEAM_SETUP}/${seg(workspace)}/plugin/versions/${seg(version)}/zip`),
146
157
  {
147
158
  headers: {
@@ -207,6 +218,10 @@ var SystemDoxApiClient = class {
207
218
  buildUrl(path, params) {
208
219
  const base = this.config.apiUrl.replace(/\/+$/, "");
209
220
  const url = new URL(`${base}${path.startsWith("/") ? "" : "/"}${path}`);
221
+ const { origin, pathname } = new URL(base);
222
+ if (walksThePath(path) || url.origin !== origin || !url.pathname.startsWith(`${pathname.replace(/\/+$/, "")}/`)) {
223
+ throw new Error("Refused: this id leaves the API's own path.");
224
+ }
210
225
  if (params) {
211
226
  for (const [key, value] of Object.entries(params)) {
212
227
  url.searchParams.set(key, value);
@@ -216,7 +231,7 @@ var SystemDoxApiClient = class {
216
231
  }
217
232
  async fetchRaw(path, params, signal) {
218
233
  const url = this.buildUrl(path, params);
219
- return fetch(url, {
234
+ return this.call(url, {
220
235
  headers: {
221
236
  Authorization: `Bearer ${this.config.apiKey}`,
222
237
  "X-API-Key": this.config.apiKey,
@@ -225,6 +240,16 @@ var SystemDoxApiClient = class {
225
240
  signal
226
241
  });
227
242
  }
243
+ /** Every call of this client, so `onAnswer` hears each answer. */
244
+ async call(url, init) {
245
+ const res = await fetch(url, init);
246
+ const { onAnswer } = this.config;
247
+ if (onAnswer) {
248
+ const refused = res.ok ? void 0 : new SystemDoxApiError(res.status, await res.clone().text());
249
+ onAnswer({ status: res.status, code: refused?.code });
250
+ }
251
+ return res;
252
+ }
228
253
  };
229
254
 
230
255
  // src/lib/memory-sync.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@systemdox/mcp-server",
3
- "version": "0.1.55",
3
+ "version": "0.1.57",
4
4
  "mcpName": "io.github.puglieseweb/systemdox",
5
5
  "type": "module",
6
6
  "description": "Give AI coding agents your architecture context over MCP — checks, specs, ADRs, project rules, and docs from SystemDox, delivered at write time",
@@ -21,6 +21,11 @@
21
21
  },
22
22
  "files": [
23
23
  "dist",
24
+ "!dist/oauth.*",
25
+ "!dist/connect.*",
26
+ "!dist/lib/aws-deps.*",
27
+ "!dist/lib/connection-store.*",
28
+ "!dist/lib/oauth-*",
24
29
  "README.md"
25
30
  ],
26
31
  "scripts": {
@@ -68,6 +73,10 @@
68
73
  "zod": "^3.25.67"
69
74
  },
70
75
  "devDependencies": {
76
+ "@aws-sdk/client-dynamodb": "3.1159.0",
77
+ "@aws-sdk/client-kms": "3.1159.0",
78
+ "@aws-sdk/client-secrets-manager": "3.1159.0",
79
+ "@aws-sdk/lib-dynamodb": "3.1159.0",
71
80
  "@types/node": "^26.0.1",
72
81
  "esbuild": "^0.25.0",
73
82
  "tsup": "^8.5.1",