@mindstone/mcp-server-microsoft-teams 0.3.0 → 0.3.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 +10 -0
- package/README.md +1 -1
- package/dist/untrusted-content.d.ts +3 -3
- package/dist/untrusted-content.js +27 -16
- package/package.json +1 -1
- package/server.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ All notable changes to this connector will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.3.1] - 2026-08-10
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Re-sync the vendored envelope helper with the hardened canonical: attribute-bearing close tags and spoofed open tags escaped.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Re-synced the vendored `<untrusted-content>` envelope helper with the canonical hardened reference: attribute-bearing close-tag variants (`</untrusted-content foo>`) and spoofed open tags inside wrapped content are now escaped, closing an envelope-breakout gap an LLM parser could read as an envelope boundary.
|
|
16
|
+
|
|
7
17
|
## [0.3.0] - 2026-08-07
|
|
8
18
|
|
|
9
19
|
### Changed
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Microsoft 365 Teams MCP server — list and read Teams chats and channel message
|
|
|
9
9
|
|
|
10
10
|
## Status
|
|
11
11
|
|
|
12
|
-
- **Version:** [0.3.
|
|
12
|
+
- **Version:** [0.3.1](./CHANGELOG.md) · [npm](https://www.npmjs.com/package/@mindstone/mcp-server-microsoft-teams)
|
|
13
13
|
- **Auth:** OAuth (host-orchestrated, shared with [`mcp-server-microsoft-mail`](../microsoft-mail/)) ([`MS_CLIENT_ID`](./server.json))
|
|
14
14
|
- **Tools:** [17](./src/tools.ts) (chats, messages, teams, channels, users, search, presence)
|
|
15
15
|
- **Surface:** cloud-api
|
|
@@ -20,15 +20,15 @@
|
|
|
20
20
|
*/
|
|
21
21
|
/**
|
|
22
22
|
* Wrap a single untrusted string in an `<untrusted-content source="…">`
|
|
23
|
-
* envelope, escaping any embedded close-tag variant so the envelope
|
|
24
|
-
* broken out of.
|
|
23
|
+
* envelope, escaping any embedded open/close-tag variant so the envelope can
|
|
24
|
+
* neither be broken out of nor have a spoofed nested envelope planted inside.
|
|
25
25
|
*
|
|
26
26
|
* `undefined` and `null` are passed through untouched so callers can apply the wrapper
|
|
27
27
|
* uniformly to optional fields without branching.
|
|
28
28
|
*
|
|
29
29
|
* Idempotent: when `text` is already a properly-shaped envelope for the SAME
|
|
30
30
|
* `source` (starts with the matching OPEN tag, ends with CLOSE, and contains no
|
|
31
|
-
* internal close-tag variants), the original string is returned unchanged so
|
|
31
|
+
* internal open/close-tag variants), the original string is returned unchanged so
|
|
32
32
|
* `wrapUntrusted(wrapUntrusted(s, src), src) === wrapUntrusted(s, src)`.
|
|
33
33
|
*/
|
|
34
34
|
export declare function wrapUntrusted(text: string | null | undefined, source: string): string | undefined;
|
|
@@ -18,34 +18,44 @@
|
|
|
18
18
|
* `untrusted-content` in any connector that talks to an external system; this
|
|
19
19
|
* file (and the call sites that import from it) is what satisfies that gate.
|
|
20
20
|
*/
|
|
21
|
-
const UNTRUSTED_CLOSE_TAG_VARIANT = /<\/untrusted-content
|
|
21
|
+
const UNTRUSTED_CLOSE_TAG_VARIANT = /<\/untrusted-content(?:\s[^>]*)?>/gi;
|
|
22
22
|
const ESCAPED_UNTRUSTED_CLOSE_TAG = '<\\/untrusted-content>';
|
|
23
|
+
const UNTRUSTED_OPEN_TAG_VARIANT = /<untrusted-content(?:\s[^>]*)?>/gi;
|
|
24
|
+
const ESCAPED_UNTRUSTED_OPEN_TAG = '<\\untrusted-content>';
|
|
23
25
|
const UNTRUSTED_ENVELOPE = /^<untrusted-content source="[^"]*">([\s\S]*)<\/untrusted-content>$/;
|
|
24
26
|
function escapeAttr(s) {
|
|
25
27
|
return s.replaceAll('&', '&').replaceAll('"', '"').replaceAll('<', '<').replaceAll('>', '>');
|
|
26
28
|
}
|
|
27
29
|
/**
|
|
28
|
-
* Rewrite every `</untrusted-content>`
|
|
29
|
-
* whitespace before `>`) inside `s`
|
|
30
|
-
*
|
|
30
|
+
* Rewrite every `</untrusted-content>` and `<untrusted-content …>` variant
|
|
31
|
+
* (case-insensitive, optional whitespace / attributes before `>`) inside `s`
|
|
32
|
+
* to a benign textual form, so an attacker who controls the wrapped content
|
|
33
|
+
* can neither terminate the envelope early nor spoof a nested open tag that a
|
|
34
|
+
* downstream parser could re-read as a fresh envelope. An LLM parsing the
|
|
35
|
+
* markup plausibly accepts attribute-bearing close forms such as
|
|
36
|
+
* `</untrusted-content foo>` — the matcher must cover them too.
|
|
31
37
|
*/
|
|
32
|
-
function
|
|
33
|
-
return s
|
|
38
|
+
function escapeTagSentinels(s) {
|
|
39
|
+
return s
|
|
40
|
+
.replace(UNTRUSTED_OPEN_TAG_VARIANT, ESCAPED_UNTRUSTED_OPEN_TAG)
|
|
41
|
+
.replace(UNTRUSTED_CLOSE_TAG_VARIANT, ESCAPED_UNTRUSTED_CLOSE_TAG);
|
|
34
42
|
}
|
|
35
|
-
function
|
|
36
|
-
return s
|
|
43
|
+
function unescapeTagSentinels(s) {
|
|
44
|
+
return s
|
|
45
|
+
.replaceAll(ESCAPED_UNTRUSTED_OPEN_TAG, '<untrusted-content>')
|
|
46
|
+
.replaceAll(ESCAPED_UNTRUSTED_CLOSE_TAG, '</untrusted-content>');
|
|
37
47
|
}
|
|
38
48
|
/**
|
|
39
49
|
* Wrap a single untrusted string in an `<untrusted-content source="…">`
|
|
40
|
-
* envelope, escaping any embedded close-tag variant so the envelope
|
|
41
|
-
* broken out of.
|
|
50
|
+
* envelope, escaping any embedded open/close-tag variant so the envelope can
|
|
51
|
+
* neither be broken out of nor have a spoofed nested envelope planted inside.
|
|
42
52
|
*
|
|
43
53
|
* `undefined` and `null` are passed through untouched so callers can apply the wrapper
|
|
44
54
|
* uniformly to optional fields without branching.
|
|
45
55
|
*
|
|
46
56
|
* Idempotent: when `text` is already a properly-shaped envelope for the SAME
|
|
47
57
|
* `source` (starts with the matching OPEN tag, ends with CLOSE, and contains no
|
|
48
|
-
* internal close-tag variants), the original string is returned unchanged so
|
|
58
|
+
* internal open/close-tag variants), the original string is returned unchanged so
|
|
49
59
|
* `wrapUntrusted(wrapUntrusted(s, src), src) === wrapUntrusted(s, src)`.
|
|
50
60
|
*/
|
|
51
61
|
export function wrapUntrusted(text, source) {
|
|
@@ -55,13 +65,14 @@ export function wrapUntrusted(text, source) {
|
|
|
55
65
|
const close = '</untrusted-content>';
|
|
56
66
|
if (text.startsWith(open) && text.endsWith(close) && text.length >= open.length + close.length) {
|
|
57
67
|
const inner = text.slice(open.length, text.length - close.length);
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
const hasVariant = UNTRUSTED_CLOSE_TAG_VARIANT.test(inner) || UNTRUSTED_OPEN_TAG_VARIANT.test(inner);
|
|
69
|
+
UNTRUSTED_CLOSE_TAG_VARIANT.lastIndex = 0; // reset stateful /g regexes
|
|
70
|
+
UNTRUSTED_OPEN_TAG_VARIANT.lastIndex = 0;
|
|
71
|
+
if (!hasVariant) {
|
|
60
72
|
return text;
|
|
61
73
|
}
|
|
62
|
-
UNTRUSTED_CLOSE_TAG_VARIANT.lastIndex = 0;
|
|
63
74
|
}
|
|
64
|
-
return `${open}${
|
|
75
|
+
return `${open}${escapeTagSentinels(text)}${close}`;
|
|
65
76
|
}
|
|
66
77
|
/**
|
|
67
78
|
* Strip one `<untrusted-content>` envelope from `text` if present, returning raw
|
|
@@ -73,7 +84,7 @@ export function unwrapUntrusted(text) {
|
|
|
73
84
|
const match = UNTRUSTED_ENVELOPE.exec(text);
|
|
74
85
|
if (!match)
|
|
75
86
|
return text;
|
|
76
|
-
return
|
|
87
|
+
return unescapeTagSentinels(match[1]);
|
|
77
88
|
}
|
|
78
89
|
/**
|
|
79
90
|
* Recursively wrap every string key and value reachable inside `value` (strings,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindstone/mcp-server-microsoft-teams",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"mcpName": "io.github.mindstone/mcp-server-microsoft-teams",
|
|
5
5
|
"description": "Microsoft 365 Teams via Graph: list chats, read/send messages, list teams/channels, presence.",
|
|
6
6
|
"license": "FSL-1.1-MIT",
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.github.mindstone/mcp-server-microsoft-teams",
|
|
4
4
|
"title": "Microsoft 365 Teams",
|
|
5
5
|
"description": "Microsoft 365 Teams via Graph: list chats, read/send messages, list teams/channels, presence.",
|
|
6
|
-
"version": "0.3.
|
|
6
|
+
"version": "0.3.1",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/mindstone/mcp-servers",
|
|
9
9
|
"source": "github",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"registryType": "npm",
|
|
15
15
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
16
16
|
"identifier": "@mindstone/mcp-server-microsoft-teams",
|
|
17
|
-
"version": "0.3.
|
|
17
|
+
"version": "0.3.1",
|
|
18
18
|
"runtimeHint": "npx",
|
|
19
19
|
"transport": {
|
|
20
20
|
"type": "stdio"
|