@mindstone/mcp-server-quickbooks
Advanced tools
@@ -23,4 +23,4 @@ /** | ||
| * Wrap a single untrusted string in an `<untrusted-content source="…">` | ||
| * envelope, escaping any embedded close-tag variant so the envelope cannot be | ||
| * broken out of. | ||
| * envelope, escaping any embedded open/close-tag variant so the envelope can | ||
| * neither be broken out of nor have a spoofed nested envelope planted inside. | ||
| * | ||
@@ -32,3 +32,3 @@ * `undefined` and `null` are passed through untouched so callers can apply the wrapper | ||
| * `source` (starts with the matching OPEN tag, ends with CLOSE, and contains no | ||
| * internal close-tag variants), the original string is returned unchanged so | ||
| * internal open/close-tag variants), the original string is returned unchanged so | ||
| * `wrapUntrusted(wrapUntrusted(s, src), src) === wrapUntrusted(s, src)`. | ||
@@ -35,0 +35,0 @@ */ |
@@ -21,4 +21,6 @@ /** | ||
| */ | ||
| const UNTRUSTED_CLOSE_TAG_VARIANT = /<\/untrusted-content\s*>/gi; | ||
| const UNTRUSTED_CLOSE_TAG_VARIANT = /<\/untrusted-content(?:\s[^>]*)?>/gi; | ||
| const ESCAPED_UNTRUSTED_CLOSE_TAG = '<\\/untrusted-content>'; | ||
| const UNTRUSTED_OPEN_TAG_VARIANT = /<untrusted-content(?:\s[^>]*)?>/gi; | ||
| const ESCAPED_UNTRUSTED_OPEN_TAG = '<\\untrusted-content>'; | ||
| const UNTRUSTED_ENVELOPE = /^<untrusted-content source="[^"]*">([\s\S]*)<\/untrusted-content>$/; | ||
@@ -29,16 +31,24 @@ function escapeAttr(s) { | ||
| /** | ||
| * Rewrite every `</untrusted-content>` variant (case-insensitive, optional | ||
| * whitespace before `>`) inside `s` to a benign textual form, so an attacker | ||
| * who controls the wrapped content cannot terminate the envelope early. | ||
| * Rewrite every `</untrusted-content>` and `<untrusted-content …>` variant | ||
| * (case-insensitive, optional whitespace / attributes before `>`) inside `s` | ||
| * to a benign textual form, so an attacker who controls the wrapped content | ||
| * can neither terminate the envelope early nor spoof a nested open tag that a | ||
| * downstream parser could re-read as a fresh envelope. An LLM parsing the | ||
| * markup plausibly accepts attribute-bearing close forms such as | ||
| * `</untrusted-content foo>` — the matcher must cover them too. | ||
| */ | ||
| function escapeCloseTagSentinels(s) { | ||
| return s.replace(UNTRUSTED_CLOSE_TAG_VARIANT, ESCAPED_UNTRUSTED_CLOSE_TAG); | ||
| function escapeTagSentinels(s) { | ||
| return s | ||
| .replace(UNTRUSTED_OPEN_TAG_VARIANT, ESCAPED_UNTRUSTED_OPEN_TAG) | ||
| .replace(UNTRUSTED_CLOSE_TAG_VARIANT, ESCAPED_UNTRUSTED_CLOSE_TAG); | ||
| } | ||
| function unescapeCloseTagSentinels(s) { | ||
| return s.replaceAll(ESCAPED_UNTRUSTED_CLOSE_TAG, '</untrusted-content>'); | ||
| function unescapeTagSentinels(s) { | ||
| return s | ||
| .replaceAll(ESCAPED_UNTRUSTED_OPEN_TAG, '<untrusted-content>') | ||
| .replaceAll(ESCAPED_UNTRUSTED_CLOSE_TAG, '</untrusted-content>'); | ||
| } | ||
| /** | ||
| * Wrap a single untrusted string in an `<untrusted-content source="…">` | ||
| * envelope, escaping any embedded close-tag variant so the envelope cannot be | ||
| * broken out of. | ||
| * envelope, escaping any embedded open/close-tag variant so the envelope can | ||
| * neither be broken out of nor have a spoofed nested envelope planted inside. | ||
| * | ||
@@ -50,3 +60,3 @@ * `undefined` and `null` are passed through untouched so callers can apply the wrapper | ||
| * `source` (starts with the matching OPEN tag, ends with CLOSE, and contains no | ||
| * internal close-tag variants), the original string is returned unchanged so | ||
| * internal open/close-tag variants), the original string is returned unchanged so | ||
| * `wrapUntrusted(wrapUntrusted(s, src), src) === wrapUntrusted(s, src)`. | ||
@@ -61,9 +71,10 @@ */ | ||
| const inner = text.slice(open.length, text.length - close.length); | ||
| if (!UNTRUSTED_CLOSE_TAG_VARIANT.test(inner)) { | ||
| UNTRUSTED_CLOSE_TAG_VARIANT.lastIndex = 0; // reset stateful /g regex | ||
| const hasVariant = UNTRUSTED_CLOSE_TAG_VARIANT.test(inner) || UNTRUSTED_OPEN_TAG_VARIANT.test(inner); | ||
| UNTRUSTED_CLOSE_TAG_VARIANT.lastIndex = 0; // reset stateful /g regexes | ||
| UNTRUSTED_OPEN_TAG_VARIANT.lastIndex = 0; | ||
| if (!hasVariant) { | ||
| return text; | ||
| } | ||
| UNTRUSTED_CLOSE_TAG_VARIANT.lastIndex = 0; | ||
| } | ||
| return `${open}${escapeCloseTagSentinels(text)}${close}`; | ||
| return `${open}${escapeTagSentinels(text)}${close}`; | ||
| } | ||
@@ -80,3 +91,3 @@ /** | ||
| return text; | ||
| return unescapeCloseTagSentinels(match[1]); | ||
| return unescapeTagSentinels(match[1]); | ||
| } | ||
@@ -83,0 +94,0 @@ /** |
+1
-1
| { | ||
| "name": "@mindstone/mcp-server-quickbooks", | ||
| "version": "0.4.1", | ||
| "version": "0.4.2", | ||
| "mcpName": "io.github.mindstone/mcp-server-quickbooks", | ||
@@ -5,0 +5,0 @@ "description": "QuickBooks Online MCP server for Model Context Protocol hosts — invoices, bills, customers, vendors, accounts", |
+1
-1
@@ -10,3 +10,3 @@ # @mindstone/mcp-server-quickbooks | ||
| - **Version:** [0.4.1](./CHANGELOG.md) · [npm](https://www.npmjs.com/package/@mindstone/mcp-server-quickbooks) | ||
| - **Version:** [0.4.2](./CHANGELOG.md) · [npm](https://www.npmjs.com/package/@mindstone/mcp-server-quickbooks) | ||
| - **Auth:** OAuth ([`QUICKBOOKS_REFRESH_TOKEN`](./server.json)) | ||
@@ -13,0 +13,0 @@ - **Tools:** [21](./src/tools/) (customers, vendors, invoices, bills, estimates, reports) |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
110295
0.72%2010
0.55%