@tollbooth-dpyc/web 1.4.0 → 1.4.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/dist/client.js +4 -4
- package/dist/debugLog.d.ts +4 -2
- package/dist/debugLog.js +56 -4
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
13
13
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
14
14
|
import { tollboothConfig, toolName } from "./config.js";
|
|
15
|
-
import { debugPush } from "./debugLog.js";
|
|
15
|
+
import { debugPush, redact } from "./debugLog.js";
|
|
16
16
|
import { clearStoredProof, forgetRecentLogin, getStoredNpub, getStoredProof, } from "./identity.js";
|
|
17
17
|
import { signInlineProof } from "./inlineProof.js";
|
|
18
18
|
import { isTransportFailure, NetworkError } from "./networkError.js";
|
|
@@ -129,7 +129,7 @@ export async function callToolWithContent(tool, args = {}, opts = {}) {
|
|
|
129
129
|
// `args` holds only the caller's own parameters — the envelope is added
|
|
130
130
|
// below — so it is safe to log.
|
|
131
131
|
if (!quiet)
|
|
132
|
-
debugPush("call", `${name}(${JSON.stringify(args).slice(0, 140)})`);
|
|
132
|
+
debugPush("call", `${name}(${redact(JSON.stringify(args)).slice(0, 140)})`);
|
|
133
133
|
const bootstrap = STANDARD_BOOTSTRAP.includes(tool) || cfg.extraBootstrapTools.includes(tool);
|
|
134
134
|
const merged = bootstrap
|
|
135
135
|
? { ...args }
|
|
@@ -154,14 +154,14 @@ export async function callToolWithContent(tool, args = {}, opts = {}) {
|
|
|
154
154
|
if (result.isError) {
|
|
155
155
|
const text = errorText(result);
|
|
156
156
|
if (!quiet)
|
|
157
|
-
debugPush("error", `${name}: ${text.slice(0, 200)}`);
|
|
157
|
+
debugPush("error", `${name}: ${redact(text).slice(0, 200)}`);
|
|
158
158
|
throw new Error(text);
|
|
159
159
|
}
|
|
160
160
|
const { payload, images } = readToolResult(result);
|
|
161
161
|
if (!quiet) {
|
|
162
162
|
const preview = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
163
163
|
const tail = images.length ? ` +${images.length} image` : "";
|
|
164
|
-
debugPush(looksFailed(payload) ? "error" : "result", `${name} → ${String(preview).slice(0, 220)}${tail}`);
|
|
164
|
+
debugPush(looksFailed(payload) ? "error" : "result", `${name} → ${redact(String(preview)).slice(0, 220)}${tail}`);
|
|
165
165
|
}
|
|
166
166
|
if (!opts.bestEffort) {
|
|
167
167
|
const bounced = proofBounceMessage(payload);
|
package/dist/debugLog.d.ts
CHANGED
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* that flips the view can still be read and copied afterward.
|
|
8
8
|
*
|
|
9
9
|
* Every message is scrubbed on the way in: an nsec, a 64-hex key, a bearer
|
|
10
|
-
* token,
|
|
11
|
-
* password, api_key, …)
|
|
10
|
+
* token, the value of any secret-named field (dpop_token, proof, poison,
|
|
11
|
+
* password, api_key, …), and a secret inside a URL (a query token, a
|
|
12
|
+
* bearer-like path segment, userinfo) never reach the buffer, the clipboard
|
|
13
|
+
* or storage.
|
|
12
14
|
* That is the one guarantee a panel relies on — it renders what is here.
|
|
13
15
|
*
|
|
14
16
|
* Merged from the fleet's copies (goodearth, beesknees, cypher, excalibur,
|
package/dist/debugLog.js
CHANGED
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* that flips the view can still be read and copied afterward.
|
|
8
8
|
*
|
|
9
9
|
* Every message is scrubbed on the way in: an nsec, a 64-hex key, a bearer
|
|
10
|
-
* token,
|
|
11
|
-
* password, api_key, …)
|
|
10
|
+
* token, the value of any secret-named field (dpop_token, proof, poison,
|
|
11
|
+
* password, api_key, …), and a secret inside a URL (a query token, a
|
|
12
|
+
* bearer-like path segment, userinfo) never reach the buffer, the clipboard
|
|
13
|
+
* or storage.
|
|
12
14
|
* That is the one guarantee a panel relies on — it renders what is here.
|
|
13
15
|
*
|
|
14
16
|
* Merged from the fleet's copies (goodearth, beesknees, cypher, excalibur,
|
|
@@ -34,7 +36,7 @@ const SECRET_FIELD = "(?:nsec|ncryptsec|dpop_token|proof|poison|password|passphr
|
|
|
34
36
|
// a bare one up to a delimiter. Stopping at the first quote of either kind let
|
|
35
37
|
// the tail of a secret holding `'` or `\"` through.
|
|
36
38
|
function fieldValue(names) {
|
|
37
|
-
return new RegExp(`((?:\\\\*["'])?\\b${names}(?:\\\\*["'])?\\s*[:=]\\s*)(?:(\\\\*["'])(?:\\\\.|\\\\$|(?!\\2)[^\\\\])*?(?=\\2|$)|[^"'\\\\,}\\]&\\s]+)`, "gi");
|
|
39
|
+
return new RegExp(`((?:\\\\*["'])?\\b${names}(?:\\\\*["'])?\\s*[:=]\\s*)(?:(\\\\*["'])(?:\\\\.|\\\\$|(?!\\2)[^\\\\])*?(?=\\2|$)|(?!\\[redacted\\])[^"'\\\\,}\\]&\\s]+)`, "gi");
|
|
38
40
|
}
|
|
39
41
|
const FIELD_VALUE = fieldValue(SECRET_FIELD);
|
|
40
42
|
// update_patron_credential / update_operator_credential carry the secret as
|
|
@@ -47,6 +49,54 @@ const BEARER = /\bBearer\s+[A-Za-z0-9._~+/=-]+/g;
|
|
|
47
49
|
function scrubbed(_match, head, quote) {
|
|
48
50
|
return `${head}${quote ?? ""}${REDACTED}`;
|
|
49
51
|
}
|
|
52
|
+
// ── Secrets inside URLs ────────────────────────────────────────────────────
|
|
53
|
+
//
|
|
54
|
+
// A capability link carries its secret in the URL itself — Good Earth's
|
|
55
|
+
// calendar feed is `https://host/calendar/<32 hex>.ics` — and a link read
|
|
56
|
+
// back in a tool result is logged whole. Three places a URL hides a secret:
|
|
57
|
+
// the userinfo, a query or fragment parameter, and a bearer-like path segment.
|
|
58
|
+
/** Query/fragment parameter names whose value is a secret (matched whole). */
|
|
59
|
+
const URL_SECRET_PARAM = /([?&#;](?:token|access_token|refresh_token|id_token|auth|authorization|key|api_key|apikey|secret|client_secret|password|pass|sig|signature|code|proof|dpop_token|session|sessionid|nsec)=)[^&#;\s"'\\<>]*/gi;
|
|
60
|
+
// A URL up to whitespace, a quote, an escape or an angle bracket — so one
|
|
61
|
+
// inside JSON (or JSON inside JSON) ends at its own closing quote.
|
|
62
|
+
const URL_IN_TEXT = /\b(?:https?|webcal|wss?):\/\/[^\s"'\\<>]+/gi;
|
|
63
|
+
const USERINFO = /^([a-z]+:\/\/)[^/?#@]+@/i;
|
|
64
|
+
/**
|
|
65
|
+
* A path segment that reads as a bearer secret rather than a name: 32+ hex,
|
|
66
|
+
* or 24+ base64url characters holding both a letter and a digit and one
|
|
67
|
+
* unbroken alphanumeric run of 16+ (a slug's words are short; a token's are
|
|
68
|
+
* not). An npub is public and stays.
|
|
69
|
+
*/
|
|
70
|
+
function isSecretSegment(seg) {
|
|
71
|
+
if (/^npub1/i.test(seg))
|
|
72
|
+
return false;
|
|
73
|
+
if (/^[0-9a-f]{32,}$/i.test(seg))
|
|
74
|
+
return true;
|
|
75
|
+
if (!/^[A-Za-z0-9_-]{24,}$/.test(seg))
|
|
76
|
+
return false;
|
|
77
|
+
if (!/[0-9]/.test(seg) || !/[A-Za-z]/.test(seg))
|
|
78
|
+
return false;
|
|
79
|
+
return /[A-Za-z0-9]{16,}/.test(seg);
|
|
80
|
+
}
|
|
81
|
+
function scrubUrl(url) {
|
|
82
|
+
const head = url.replace(USERINFO, `$1${REDACTED}@`);
|
|
83
|
+
const pathStart = head.indexOf("/", head.indexOf("://") + 3);
|
|
84
|
+
if (pathStart < 0)
|
|
85
|
+
return head;
|
|
86
|
+
const tailAt = head.slice(pathStart).search(/[?#]/);
|
|
87
|
+
const pathEnd = tailAt < 0 ? head.length : pathStart + tailAt;
|
|
88
|
+
const path = head
|
|
89
|
+
.slice(pathStart, pathEnd)
|
|
90
|
+
.split("/")
|
|
91
|
+
.map((seg) => {
|
|
92
|
+
// `<token>.ics`: judge the name, keep the extension.
|
|
93
|
+
const dot = seg.indexOf(".");
|
|
94
|
+
const name = dot < 0 ? seg : seg.slice(0, dot);
|
|
95
|
+
return isSecretSegment(name) ? REDACTED + (dot < 0 ? "" : seg.slice(dot)) : seg;
|
|
96
|
+
})
|
|
97
|
+
.join("/");
|
|
98
|
+
return head.slice(0, pathStart) + path + head.slice(pathEnd);
|
|
99
|
+
}
|
|
50
100
|
/**
|
|
51
101
|
* Scrub secrets from a log line. Deliberately over-eager: a 64-hex event id
|
|
52
102
|
* is hidden along with a hex private key, because the two look alike.
|
|
@@ -55,7 +105,9 @@ export function redact(message) {
|
|
|
55
105
|
let out = message
|
|
56
106
|
.replace(NSEC, REDACTED)
|
|
57
107
|
.replace(BEARER, `Bearer ${REDACTED}`)
|
|
58
|
-
.replace(
|
|
108
|
+
.replace(URL_IN_TEXT, scrubUrl)
|
|
109
|
+
.replace(FIELD_VALUE, scrubbed)
|
|
110
|
+
.replace(URL_SECRET_PARAM, `$1${REDACTED}`);
|
|
59
111
|
if (HAS_FIELD.test(out))
|
|
60
112
|
out = out.replace(CREDENTIAL_VALUE, scrubbed);
|
|
61
113
|
return out.replace(HEX_KEY, REDACTED);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tollbooth-dpyc/web",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Shared browser SDK for Tollbooth DPYC operator front ends: npub sign-in, proofs, MCP client, profile and wallet",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Lonnie VanZandt",
|