swarmbox 0.11.0 → 0.13.0
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/README.md +27 -1
- package/dist/fleet/browser.d.ts +10 -4
- package/dist/fleet/browser.d.ts.map +1 -1
- package/dist/fleet/browser.js +15 -5
- package/dist/fleet/browser.js.map +1 -1
- package/dist/fleet/sight.d.ts +45 -0
- package/dist/fleet/sight.d.ts.map +1 -1
- package/dist/fleet/sight.js +185 -2
- package/dist/fleet/sight.js.map +1 -1
- package/dist/harness.d.ts +55 -1
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js +204 -37
- package/dist/harness.js.map +1 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +5 -1
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +77 -5
- package/dist/mcp/server.js.map +1 -1
- package/dist/product.d.ts +19 -3
- package/dist/product.d.ts.map +1 -1
- package/dist/redact.d.ts +13 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +226 -0
- package/dist/redact.js.map +1 -0
- package/dist/requests.d.ts +60 -0
- package/dist/requests.d.ts.map +1 -0
- package/dist/requests.js +137 -0
- package/dist/requests.js.map +1 -0
- package/dist/standins/builtin/email/index.d.ts.map +1 -1
- package/dist/standins/builtin/email/index.js +238 -8
- package/dist/standins/builtin/email/index.js.map +1 -1
- package/dist/standins/builtin/email/manifest.json +6 -2
- package/dist/standins/runner.d.ts.map +1 -1
- package/dist/standins/runner.js +25 -9
- package/dist/standins/runner.js.map +1 -1
- package/dist/standins/sdk.js +8 -0
- package/dist/standins/sdk.js.map +1 -1
- package/package.json +2 -2
package/dist/product.d.ts
CHANGED
|
@@ -37,7 +37,11 @@ export interface WorldSpec {
|
|
|
37
37
|
export interface StandInEndpoint {
|
|
38
38
|
/** The stand-in's base addresses by the manifest's names: point the product's real client at these. */
|
|
39
39
|
baseUrls: Record<string, string>;
|
|
40
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* A provider account for one tenant: credentials the product provisions that tenant with, all marked fake. Asked
|
|
42
|
+
* again for the same tenant: the same credentials, with the settings given changed (a webhook the product only
|
|
43
|
+
* knows once it has connected the account), as a person would change them in the provider's console.
|
|
44
|
+
*/
|
|
41
45
|
account(tenant: string, settings?: Record<string, string>): Promise<Record<string, string>>;
|
|
42
46
|
}
|
|
43
47
|
/** Where a stand-in comes from: its folder holds manifest.json and its entry module. */
|
|
@@ -98,6 +102,10 @@ export interface ProductWorld {
|
|
|
98
102
|
/**
|
|
99
103
|
* After the world's clock moves, the cookies that keep a screen already signed in signed in on the new
|
|
100
104
|
* time, for a product whose sessions expire. Without it, a jump past a session's life signs the screen out.
|
|
105
|
+
* Prefer extending the screen's own session (its expiry, on the world's time) and returning [] over minting a
|
|
106
|
+
* new one: a page that keeps state derived from its session (a CSRF token, a session id in memory) would send
|
|
107
|
+
* the old with the new cookie, and be signed out by the product's own checks. Return new cookies only when
|
|
108
|
+
* the product's sessions carry nothing the page keeps.
|
|
101
109
|
*/
|
|
102
110
|
renewSignIn?(persona: Persona, app: string | undefined): Promise<StartCookie[]>;
|
|
103
111
|
/**
|
|
@@ -186,10 +194,18 @@ export interface Product<W extends ProductWorld = ProductWorld> {
|
|
|
186
194
|
/** Tenants' hosts are `<tenant>.<baseDomain>`, known before a world starts: needed for stand-ins. */
|
|
187
195
|
baseDomain?: string;
|
|
188
196
|
/**
|
|
189
|
-
* Origins outside the world the product's pages load from (web fonts, maps).
|
|
190
|
-
*
|
|
197
|
+
* Origins outside the world the product's pages load from (web fonts, maps). Giving them contains the world's
|
|
198
|
+
* browsers (see `contain`): they reach only the world's own hosts, its stand-ins and these; the rest is blocked.
|
|
191
199
|
*/
|
|
192
200
|
browserAllow?: string[];
|
|
201
|
+
/**
|
|
202
|
+
* Keep a world's browsers inside it: the world's own hosts, its stand-ins and `browserAllow`, and nothing else
|
|
203
|
+
* (no request, WebSocket or service worker reaches out, this machine's other servers included). Default: on when
|
|
204
|
+
* `browserAllow` is given, off otherwise; always on while a stand-in plays a provider's pages. A product whose
|
|
205
|
+
* pages know a real outside address (an identity provider, an API) should say true: off, a signed-out page can
|
|
206
|
+
* reach it.
|
|
207
|
+
*/
|
|
208
|
+
contain?: boolean;
|
|
193
209
|
/**
|
|
194
210
|
* Options of the product's own for a world, as zod fields with descriptions: a tenant still migrating
|
|
195
211
|
* from another system, a feature switched on. world_create and a scenario's world.options take them.
|
package/dist/product.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"product.d.ts","sourceRoot":"","sources":["../src/product.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,6FAA6F;AAC7F,MAAM,WAAW,OAAO;IACtB,iGAAiG;IACjG,EAAE,EAAE,MAAM,CAAC;IACX,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,6FAA6F;IAC7F,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,yFAAyF;IACzF,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,mCAAmC;AACnC,MAAM,WAAW,SAAS;IACxB,wEAAwE;IACxE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,oCAAoC;IACpC,IAAI,EAAE,OAAO,CAAC;IACd,+EAA+E;IAC/E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qGAAqG;IACrG,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,qFAAqF;IACrF,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,gGAAgG;AAChG,MAAM,WAAW,eAAe;IAC9B,uGAAuG;IACvG,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC
|
|
1
|
+
{"version":3,"file":"product.d.ts","sourceRoot":"","sources":["../src/product.ts"],"names":[],"mappings":"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"}
|
package/dist/redact.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const REDACTED = "[redacted]";
|
|
2
|
+
/** By its words, so that "two_signature_threshold" is not a secret and "x-api-key" or "AuthToken" is. */
|
|
3
|
+
export declare function secretName(name: string): boolean;
|
|
4
|
+
/** An address with its secret query values replaced; on `origin`, only its path and query. */
|
|
5
|
+
export declare function redactUrl(url: string, origin?: string): string;
|
|
6
|
+
/** A path and query (as a server sees a request) with its secrets replaced. */
|
|
7
|
+
export declare function redactTarget(target: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* A body with its secrets replaced: JSON by key (and name/value pairs), a form by field (a multipart one by
|
|
10
|
+
* part), XML by element, attribute and name/value pair, anything else only if it is one token.
|
|
11
|
+
*/
|
|
12
|
+
export declare function redactBody(body: string, contentType?: string): string;
|
|
13
|
+
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAqCA,eAAO,MAAM,QAAQ,eAAe,CAAC;AAErC,yGAAyG;AACzG,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAYhD;AAMD,8FAA8F;AAC9F,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAe9D;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAInD;AA8ID;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,SAAK,GAAG,MAAM,CAgBjE"}
|
package/dist/redact.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Secrets out of what Swarmbox keeps and shows: a screen's requests (requests.ts) and a stand-in's log
|
|
2
|
+
// (standins/runner.ts). Both reach the agent, and from the agent a finding, run.json and the report, so a value
|
|
3
|
+
// is replaced before it is stored, never after.
|
|
4
|
+
//
|
|
5
|
+
// A value is a secret's when its name says so, or when it is shaped like a token. Names are read in JSON, in a
|
|
6
|
+
// form (urlencoded or multipart), in a query string, in XML elements and attributes, and in name/value pairs (a
|
|
7
|
+
// report server's <Name>Password</Name><Value>…</Value>, {"name": "password", "value": "…"}, or a validation
|
|
8
|
+
// error's {"loc": ["body", "password"], "input": "…"}).
|
|
9
|
+
//
|
|
10
|
+
// Bodies run to a megabyte and are read on the server's own thread, so every pattern is bounded: none may
|
|
11
|
+
// backtrack its way through a body built to make it.
|
|
12
|
+
/** A name is a secret's when its last word is one of these, or its last two are one of the pairs. */
|
|
13
|
+
const SECRET_WORDS = new Set([
|
|
14
|
+
'password',
|
|
15
|
+
'passwd',
|
|
16
|
+
'pwd',
|
|
17
|
+
'passcode',
|
|
18
|
+
'passphrase',
|
|
19
|
+
'secret',
|
|
20
|
+
'token',
|
|
21
|
+
'auth',
|
|
22
|
+
'authorization',
|
|
23
|
+
'cookie',
|
|
24
|
+
'session',
|
|
25
|
+
'signature',
|
|
26
|
+
'credential',
|
|
27
|
+
'credentials',
|
|
28
|
+
'otp',
|
|
29
|
+
'apikey',
|
|
30
|
+
]);
|
|
31
|
+
const SECRET_PAIRS = new Set(['apikey', 'sessionid', 'accesskey', 'privatekey', 'secretkey']);
|
|
32
|
+
/** A last word run together from others ("dbpassword", "csrftoken") still ends in its secret. */
|
|
33
|
+
const SECRET_ENDINGS = ['password', 'passwd', 'passphrase', 'secret', 'token'];
|
|
34
|
+
/** A JWT or JWE (base64url segments, the first a JSON header), or an opaque string long enough to be a key. */
|
|
35
|
+
const TOKEN_SHAPED = /^(eyJ[\w-]*\.[\w.-]+|(Bearer|Basic)\s+\S+|[A-Za-z0-9_+/=-]{40,})$/;
|
|
36
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
37
|
+
export const REDACTED = '[redacted]';
|
|
38
|
+
/** By its words, so that "two_signature_threshold" is not a secret and "x-api-key" or "AuthToken" is. */
|
|
39
|
+
export function secretName(name) {
|
|
40
|
+
const words = name
|
|
41
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
42
|
+
.toLowerCase()
|
|
43
|
+
.split(/[^a-z0-9]+/)
|
|
44
|
+
.filter(Boolean);
|
|
45
|
+
const last = words.at(-1) ?? '';
|
|
46
|
+
return (SECRET_WORDS.has(last) ||
|
|
47
|
+
SECRET_PAIRS.has(words.slice(-2).join('')) ||
|
|
48
|
+
SECRET_ENDINGS.some((w) => last.endsWith(w)));
|
|
49
|
+
}
|
|
50
|
+
function secretValue(name, value) {
|
|
51
|
+
return secretName(name) || (TOKEN_SHAPED.test(value) && !UUID.test(value));
|
|
52
|
+
}
|
|
53
|
+
/** An address with its secret query values replaced; on `origin`, only its path and query. */
|
|
54
|
+
export function redactUrl(url, origin) {
|
|
55
|
+
let u;
|
|
56
|
+
try {
|
|
57
|
+
u = new URL(url);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return url;
|
|
61
|
+
}
|
|
62
|
+
for (const [k, v] of [...u.searchParams])
|
|
63
|
+
if (secretValue(k, v))
|
|
64
|
+
u.searchParams.set(k, REDACTED);
|
|
65
|
+
// A token in the path, as a customer's link carries one.
|
|
66
|
+
u.pathname = u.pathname
|
|
67
|
+
.split('/')
|
|
68
|
+
.map((seg) => (TOKEN_SHAPED.test(seg) && !UUID.test(seg) ? REDACTED : seg))
|
|
69
|
+
.join('/');
|
|
70
|
+
const shown = u.toString().replaceAll(encodeURIComponent(REDACTED), REDACTED);
|
|
71
|
+
return origin && u.origin === origin ? shown.slice(origin.length) || '/' : shown;
|
|
72
|
+
}
|
|
73
|
+
/** A path and query (as a server sees a request) with its secrets replaced. */
|
|
74
|
+
export function redactTarget(target) {
|
|
75
|
+
const here = 'http://redact.invalid';
|
|
76
|
+
if (/^https?:\/\//i.test(target))
|
|
77
|
+
return redactUrl(target);
|
|
78
|
+
return target.startsWith('/') ? redactUrl(`${here}${target}`, here) : target;
|
|
79
|
+
}
|
|
80
|
+
/** The keys that name a name/value pair's secret, in any case: {"Name": "Password", "Value": "…"}. */
|
|
81
|
+
const PAIR_NAME = new Set(['name', 'key', 'field', 'parameter']);
|
|
82
|
+
/** The keys that hold its value: a validation error echoes what it refused as "input". */
|
|
83
|
+
const PAIR_VALUE = new Set(['value', 'input']);
|
|
84
|
+
/** What a pair is named: by one of its name keys, or by the last word of a validation error's loc. */
|
|
85
|
+
function pairName(o) {
|
|
86
|
+
for (const [k, x] of Object.entries(o)) {
|
|
87
|
+
const key = k.toLowerCase();
|
|
88
|
+
if (PAIR_NAME.has(key) && typeof x === 'string')
|
|
89
|
+
return x;
|
|
90
|
+
if (key === 'loc' && Array.isArray(x)) {
|
|
91
|
+
const last = x.at(-1);
|
|
92
|
+
if (typeof last === 'string')
|
|
93
|
+
return last;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
function redactJson(v, name = '') {
|
|
99
|
+
if (typeof v === 'string')
|
|
100
|
+
return secretValue(name, v) ? REDACTED : v;
|
|
101
|
+
if (Array.isArray(v))
|
|
102
|
+
return v.map((x) => redactJson(x, name));
|
|
103
|
+
if (v && typeof v === 'object') {
|
|
104
|
+
const o = v;
|
|
105
|
+
const named = pairName(o);
|
|
106
|
+
const pair = named !== undefined && secretName(named);
|
|
107
|
+
return Object.fromEntries(Object.entries(o).map(([k, x]) => (secretName(k) || (pair && PAIR_VALUE.has(k.toLowerCase()))) && x !== null && typeof x !== 'object'
|
|
108
|
+
? [k, REDACTED]
|
|
109
|
+
: [k, redactJson(x, k)]));
|
|
110
|
+
}
|
|
111
|
+
return v;
|
|
112
|
+
}
|
|
113
|
+
const decoded = (s) => {
|
|
114
|
+
try {
|
|
115
|
+
return decodeURIComponent(s.replace(/\+/g, ' '));
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return s;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
/** A form with its secret fields' values replaced and every other byte as it was. */
|
|
122
|
+
function redactForm(t) {
|
|
123
|
+
return t
|
|
124
|
+
.split('&')
|
|
125
|
+
.map((field) => {
|
|
126
|
+
const at = field.indexOf('=');
|
|
127
|
+
if (at < 0)
|
|
128
|
+
return field;
|
|
129
|
+
const name = field.slice(0, at);
|
|
130
|
+
return secretValue(decoded(name), decoded(field.slice(at + 1))) ? `${name}=${REDACTED}` : field;
|
|
131
|
+
})
|
|
132
|
+
.join('&');
|
|
133
|
+
}
|
|
134
|
+
/** A multipart form's parts, each a secret's when its name is. */
|
|
135
|
+
function redactMultipart(body, boundary) {
|
|
136
|
+
const sep = `--${boundary}`;
|
|
137
|
+
return body
|
|
138
|
+
.split(sep)
|
|
139
|
+
.map((part) => {
|
|
140
|
+
const gap = /\r?\n\r?\n/.exec(part);
|
|
141
|
+
if (!gap)
|
|
142
|
+
return part;
|
|
143
|
+
const headers = part.slice(0, gap.index);
|
|
144
|
+
const content = part.slice(gap.index + gap[0].length);
|
|
145
|
+
// The part ends at the line break before the next boundary.
|
|
146
|
+
const end = /\r?\n$/.exec(content)?.[0] ?? '';
|
|
147
|
+
const value = content.slice(0, content.length - end.length);
|
|
148
|
+
const name = /\bname="([^"]*)"/i.exec(headers)?.[1];
|
|
149
|
+
const type = /^content-type:[ \t]*([^\r\n]*)/im.exec(headers)?.[1] ?? '';
|
|
150
|
+
const kept = name !== undefined && secretName(name) ? REDACTED : redactBody(value, type);
|
|
151
|
+
return `${headers}${gap[0]}${kept}${end}`;
|
|
152
|
+
})
|
|
153
|
+
.join(sep);
|
|
154
|
+
}
|
|
155
|
+
const FORM_SHAPED = /^[^\s=&<>{}]+=[^\s&]*(&[^\s=&]+=[^\s&]*)*$/;
|
|
156
|
+
/** <ns:Password attr="…">value</ns:Password>: the value. */
|
|
157
|
+
const XML_ELEMENT = /<((?:[\w.-]{1,64}:)?([\w.-]{1,128}))(\s[^<>]{0,2048})?>([^<]*)<\/\1\s{0,16}>/g;
|
|
158
|
+
/** <Name xsi:type="…">Password</Name> <Value>value</Value>: the value. */
|
|
159
|
+
const XML_PAIR = new RegExp('(<((?:[\\w.-]{1,64}:)?Name)(?:\\s[^<>]{0,2048})?>([^<]{0,256})<\\/\\2\\s{0,16}>\\s{0,256}' +
|
|
160
|
+
'<((?:[\\w.-]{1,64}:)?Value)(?:\\s[^<>]{0,2048})?>)([^<]*)(<\\/\\4\\s{0,16}>)', 'gi');
|
|
161
|
+
/** Where a CDATA section was set aside: its number between two private-use characters. */
|
|
162
|
+
const SET_ASIDE = /\uE000(\d+)\uE001/g;
|
|
163
|
+
/**
|
|
164
|
+
* The text with each CDATA section set aside (its value may hold "<"), so an element whose value is one reads
|
|
165
|
+
* as an element with text. Found by indexOf, so a section never closed is never rescanned.
|
|
166
|
+
*/
|
|
167
|
+
function setAsideCdata(t) {
|
|
168
|
+
const sections = [];
|
|
169
|
+
if (t.includes('\uE000'))
|
|
170
|
+
return { text: t, sections };
|
|
171
|
+
let text = '';
|
|
172
|
+
let at = 0;
|
|
173
|
+
for (let open = t.indexOf('<![CDATA['); open >= 0; open = t.indexOf('<![CDATA[', at)) {
|
|
174
|
+
const close = t.indexOf(']]>', open + 9);
|
|
175
|
+
if (close < 0)
|
|
176
|
+
break;
|
|
177
|
+
text += `${t.slice(at, open)}\uE000${sections.length}\uE001`;
|
|
178
|
+
sections.push(t.slice(open, close + 3));
|
|
179
|
+
at = close + 3;
|
|
180
|
+
}
|
|
181
|
+
return { text: text + t.slice(at), sections };
|
|
182
|
+
}
|
|
183
|
+
/** A tag, up to the next angle bracket, so a run of "<" never rescans the rest of the body. */
|
|
184
|
+
const XML_TAG = /<[^<>]*>/g;
|
|
185
|
+
/** password="value" inside a tag. */
|
|
186
|
+
const XML_ATTRIBUTE = /(\s([\w.:-]{1,128})\s{0,16}=\s{0,16})("[^"]*"|'[^']*')/g;
|
|
187
|
+
/** "password": "value" in JSON that could not be read whole (cut short), the value perhaps unfinished. */
|
|
188
|
+
const JSON_PAIR = /("([^"\\]{1,100})"\s*:\s*)"((?:[^"\\]|\\.)*)("?)/g;
|
|
189
|
+
/** Text that is not whole JSON or a form: XML and SOAP, JSON cut short, or one token. */
|
|
190
|
+
function redactText(t) {
|
|
191
|
+
let out = t;
|
|
192
|
+
if (out.includes('<')) {
|
|
193
|
+
const { text, sections } = setAsideCdata(out);
|
|
194
|
+
out = text
|
|
195
|
+
.replace(XML_PAIR, (m, open, _n, name, _v, _value, close) => secretName(name.trim()) ? `${open}${REDACTED}${close}` : m)
|
|
196
|
+
.replace(XML_ELEMENT, (m, tag, local, attrs, value) => secretName(local) && value.trim() ? `<${tag}${attrs ?? ''}>${REDACTED}</${tag}>` : m)
|
|
197
|
+
.replace(XML_TAG, (tag) => tag.replace(XML_ATTRIBUTE, (m, lead, name, q) => secretName(name) ? `${lead}${q[0]}${REDACTED}${q[0]}` : m))
|
|
198
|
+
.replace(SET_ASIDE, (m, i) => sections[Number(i)] ?? m);
|
|
199
|
+
}
|
|
200
|
+
out = out.replace(JSON_PAIR, (m, lead, name, value, end) => secretValue(name, value) ? `${lead}"${REDACTED}${end}` : m);
|
|
201
|
+
return TOKEN_SHAPED.test(out.trim()) && !UUID.test(out.trim()) ? REDACTED : out;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* A body with its secrets replaced: JSON by key (and name/value pairs), a form by field (a multipart one by
|
|
205
|
+
* part), XML by element, attribute and name/value pair, anything else only if it is one token.
|
|
206
|
+
*/
|
|
207
|
+
export function redactBody(body, contentType = '') {
|
|
208
|
+
const multipart = /multipart\/form-data/i.test(contentType)
|
|
209
|
+
? /boundary=(?:"([^"]{1,200})"|([^;\s]{1,200}))/i.exec(contentType)
|
|
210
|
+
: null;
|
|
211
|
+
if (multipart)
|
|
212
|
+
return redactMultipart(body, multipart[1] ?? multipart[2]);
|
|
213
|
+
const t = body.trim();
|
|
214
|
+
if (/json/.test(contentType) || /^[[{]/.test(t)) {
|
|
215
|
+
try {
|
|
216
|
+
return JSON.stringify(redactJson(JSON.parse(t)));
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Not JSON after all, or cut short: treated as text below.
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (/x-www-form-urlencoded/.test(contentType) || (!/xml|json/.test(contentType) && FORM_SHAPED.test(t)))
|
|
223
|
+
return redactForm(t);
|
|
224
|
+
return redactText(body);
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Request as PwRequest, Response as PwResponse } from 'playwright-core';
|
|
2
|
+
import { redactBody, redactUrl } from './redact.ts';
|
|
3
|
+
export { redactBody, redactUrl };
|
|
4
|
+
export interface RequestRecord {
|
|
5
|
+
/** Counts the screen's requests from 1, so a later read can ask for what came after. */
|
|
6
|
+
seq: number;
|
|
7
|
+
at: string;
|
|
8
|
+
method: string;
|
|
9
|
+
/** Path and query on the screen's own origin; the whole address elsewhere. Secrets redacted. */
|
|
10
|
+
url: string;
|
|
11
|
+
/** Playwright's resource type: document, fetch, xhr, eventsource, websocket, script, image… */
|
|
12
|
+
type: string;
|
|
13
|
+
state: 'open' | 'done' | 'failed';
|
|
14
|
+
/** Once the response's headers arrive. */
|
|
15
|
+
status?: number;
|
|
16
|
+
/** To the response's headers, then to its end (or failure). */
|
|
17
|
+
ms?: number;
|
|
18
|
+
failure?: string;
|
|
19
|
+
contentType?: string;
|
|
20
|
+
sent?: string;
|
|
21
|
+
got?: string;
|
|
22
|
+
}
|
|
23
|
+
/** Kept per screen; the oldest go first. */
|
|
24
|
+
export declare const REQUESTS_KEPT = 300;
|
|
25
|
+
/** Of each body, kept and shown. */
|
|
26
|
+
export declare const BODY_KEPT = 2000;
|
|
27
|
+
export interface RequestLog {
|
|
28
|
+
records: RequestRecord[];
|
|
29
|
+
/** The record a response belongs to, for the readout's refusal lines. */
|
|
30
|
+
seqOf(r: PwRequest): number | undefined;
|
|
31
|
+
/** The last record's number: an action's requests are the ones after it. */
|
|
32
|
+
last(): number;
|
|
33
|
+
onRequest(r: PwRequest): void;
|
|
34
|
+
onResponse(r: PwResponse): void;
|
|
35
|
+
onFinished(r: PwRequest): Promise<void>;
|
|
36
|
+
onFailed(r: PwRequest): void;
|
|
37
|
+
}
|
|
38
|
+
/** A screen's log; `origin` is its own, whose addresses are shown as paths. */
|
|
39
|
+
export declare function requestLog(origin: string): RequestLog;
|
|
40
|
+
/**
|
|
41
|
+
* The requests started after `after` that an action should still wait for: open ones the page asked for, not
|
|
42
|
+
* a live stream (an event stream never ends, by design).
|
|
43
|
+
*/
|
|
44
|
+
export declare function pendingSince(records: RequestRecord[], after: number): RequestRecord[];
|
|
45
|
+
export interface RequestQuery {
|
|
46
|
+
/** Only addresses containing this (case-insensitive). */
|
|
47
|
+
match?: string;
|
|
48
|
+
/** Only those after this seq. */
|
|
49
|
+
after?: number;
|
|
50
|
+
/** Pages' scripts, styles, images and fonts too. */
|
|
51
|
+
all?: boolean;
|
|
52
|
+
/** What was sent and what came back, not only the lines. */
|
|
53
|
+
bodies?: boolean;
|
|
54
|
+
limit?: number;
|
|
55
|
+
}
|
|
56
|
+
/** One request as the agent reads it. */
|
|
57
|
+
export declare function requestLine(r: RequestRecord, now?: number, bodies?: boolean): string;
|
|
58
|
+
/** The records a query asks for, newest last, at most `limit` (the most recent). */
|
|
59
|
+
export declare function selectRequests(records: RequestRecord[], q?: RequestQuery): RequestRecord[];
|
|
60
|
+
//# sourceMappingURL=requests.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requests.d.ts","sourceRoot":"","sources":["../src/requests.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,IAAI,SAAS,EAAE,QAAQ,IAAI,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACpF,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;AAEjC,MAAM,WAAW,aAAa;IAC5B,wFAAwF;IACxF,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,gGAAgG;IAChG,GAAG,EAAE,MAAM,CAAC;IACZ,+FAA+F;IAC/F,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;IAClC,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,4CAA4C;AAC5C,eAAO,MAAM,aAAa,MAAM,CAAC;AACjC,oCAAoC;AACpC,eAAO,MAAM,SAAS,OAAO,CAAC;AAoB9B,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,yEAAyE;IACzE,KAAK,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;IACxC,4EAA4E;IAC5E,IAAI,IAAI,MAAM,CAAC;IACf,SAAS,CAAC,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC9B,UAAU,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,UAAU,CAAC,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,QAAQ,CAAC,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;CAC9B;AAED,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CA8DrD;AAKD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,EAAE,CAKrF;AAED,MAAM,WAAW,YAAY;IAC3B,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,4DAA4D;IAC5D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD,yCAAyC;AACzC,wBAAgB,WAAW,CAAC,CAAC,EAAE,aAAa,EAAE,GAAG,SAAa,EAAE,MAAM,UAAQ,GAAG,MAAM,CAatF;AAED,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,CAAC,GAAE,YAAiB,GAAG,aAAa,EAAE,CAS9F"}
|
package/dist/requests.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// What a screen asked its servers for, newest last: method, address, status, how long it took, and the start of
|
|
2
|
+
// what was sent and what came back. The readout after an action names refusals in one line; this is where the
|
|
3
|
+
// agent reads the rest of them, and sees what a page keeps open (a live stream) or keeps asking for (polling).
|
|
4
|
+
//
|
|
5
|
+
// Nothing secret is kept: no headers at all (cookies, authorization), and in addresses and bodies any value
|
|
6
|
+
// whose name says it is a secret, and any value shaped like a token, is replaced before it is stored (redact.ts).
|
|
7
|
+
import { redactBody, redactUrl } from "./redact.js";
|
|
8
|
+
export { redactBody, redactUrl };
|
|
9
|
+
/** Kept per screen; the oldest go first. */
|
|
10
|
+
export const REQUESTS_KEPT = 300;
|
|
11
|
+
/** Of each body, kept and shown. */
|
|
12
|
+
export const BODY_KEPT = 2000;
|
|
13
|
+
/** Loaded with the page, not asked for by it: left out unless every request is wanted. */
|
|
14
|
+
const ASSETS = new Set(['image', 'font', 'stylesheet', 'script', 'media', 'manifest', 'texttrack', 'other']);
|
|
15
|
+
/** Bodies worth reading back: what an app's own calls answer with. */
|
|
16
|
+
const READ_BODY = new Set(['fetch', 'xhr', 'document']);
|
|
17
|
+
function bounded(s) {
|
|
18
|
+
return s.length > BODY_KEPT ? `${s.slice(0, BODY_KEPT)}… (${s.length} characters)` : s;
|
|
19
|
+
}
|
|
20
|
+
function mainFrame(r) {
|
|
21
|
+
try {
|
|
22
|
+
return r.frame().parentFrame() === null;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// A service worker's request has no frame.
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** A screen's log; `origin` is its own, whose addresses are shown as paths. */
|
|
30
|
+
export function requestLog(origin) {
|
|
31
|
+
const records = [];
|
|
32
|
+
const live = new WeakMap();
|
|
33
|
+
let seq = 0;
|
|
34
|
+
return {
|
|
35
|
+
records,
|
|
36
|
+
seqOf: (r) => live.get(r)?.rec.seq,
|
|
37
|
+
last: () => seq,
|
|
38
|
+
onRequest(r) {
|
|
39
|
+
// A page load ends what the page before it had open (a live stream, a long poll), and Chromium reports
|
|
40
|
+
// no end for a response it had already started streaming.
|
|
41
|
+
if (r.isNavigationRequest() && mainFrame(r))
|
|
42
|
+
for (const rec of records)
|
|
43
|
+
if (rec.state === 'open') {
|
|
44
|
+
rec.state = 'failed';
|
|
45
|
+
rec.failure = 'cut off by a page load';
|
|
46
|
+
rec.ms = Date.now() - Date.parse(rec.at);
|
|
47
|
+
}
|
|
48
|
+
const contentType = r.headers()['content-type'];
|
|
49
|
+
const post = r.postData();
|
|
50
|
+
const rec = {
|
|
51
|
+
seq: ++seq,
|
|
52
|
+
at: new Date().toISOString(),
|
|
53
|
+
method: r.method(),
|
|
54
|
+
url: redactUrl(r.url(), origin),
|
|
55
|
+
type: r.resourceType(),
|
|
56
|
+
state: 'open',
|
|
57
|
+
...(post ? { sent: bounded(redactBody(post, contentType)) } : {}),
|
|
58
|
+
};
|
|
59
|
+
live.set(r, { rec, started: Date.now() });
|
|
60
|
+
records.push(rec);
|
|
61
|
+
if (records.length > REQUESTS_KEPT)
|
|
62
|
+
records.shift();
|
|
63
|
+
},
|
|
64
|
+
onResponse(r) {
|
|
65
|
+
const it = live.get(r.request());
|
|
66
|
+
if (!it)
|
|
67
|
+
return;
|
|
68
|
+
it.rec.status = r.status();
|
|
69
|
+
it.rec.ms = Date.now() - it.started;
|
|
70
|
+
const ct = r.headers()['content-type'];
|
|
71
|
+
if (ct)
|
|
72
|
+
it.rec.contentType = ct.split(';')[0].trim();
|
|
73
|
+
},
|
|
74
|
+
async onFinished(r) {
|
|
75
|
+
const it = live.get(r);
|
|
76
|
+
if (!it)
|
|
77
|
+
return;
|
|
78
|
+
it.rec.state = 'done';
|
|
79
|
+
it.rec.ms = Date.now() - it.started;
|
|
80
|
+
const res = await r.response().catch(() => null);
|
|
81
|
+
const ct = it.rec.contentType ?? '';
|
|
82
|
+
if (!res || !READ_BODY.has(it.rec.type) || !/json|text|xml|javascript|x-component/.test(ct))
|
|
83
|
+
return;
|
|
84
|
+
// A page the app was sent to is the screen itself: screen_read shows it better.
|
|
85
|
+
if (it.rec.type === 'document' && res.status() < 400)
|
|
86
|
+
return;
|
|
87
|
+
const body = await res.text().catch(() => null);
|
|
88
|
+
if (body)
|
|
89
|
+
it.rec.got = bounded(redactBody(body, ct));
|
|
90
|
+
},
|
|
91
|
+
onFailed(r) {
|
|
92
|
+
const it = live.get(r);
|
|
93
|
+
if (!it)
|
|
94
|
+
return;
|
|
95
|
+
it.rec.state = 'failed';
|
|
96
|
+
it.rec.ms = Date.now() - it.started;
|
|
97
|
+
it.rec.failure = r.failure()?.errorText ?? 'failed';
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** Kinds an action waits for: what the page asked its servers for, and a page load. */
|
|
102
|
+
const AWAITED = new Set(['fetch', 'xhr', 'document']);
|
|
103
|
+
/**
|
|
104
|
+
* The requests started after `after` that an action should still wait for: open ones the page asked for, not
|
|
105
|
+
* a live stream (an event stream never ends, by design).
|
|
106
|
+
*/
|
|
107
|
+
export function pendingSince(records, after) {
|
|
108
|
+
return records.filter((r) => r.seq > after && r.state === 'open' && AWAITED.has(r.type) && r.contentType !== 'text/event-stream');
|
|
109
|
+
}
|
|
110
|
+
function seconds(ms) {
|
|
111
|
+
return ms < 1000 ? `${ms} ms` : `${(ms / 1000).toFixed(1)} s`;
|
|
112
|
+
}
|
|
113
|
+
/** One request as the agent reads it. */
|
|
114
|
+
export function requestLine(r, now = Date.now(), bodies = false) {
|
|
115
|
+
const time = r.at.slice(11, 23);
|
|
116
|
+
const result = r.state === 'failed'
|
|
117
|
+
? `failed: ${r.failure} (${seconds(r.ms ?? 0)})`
|
|
118
|
+
: r.state === 'open'
|
|
119
|
+
? `${r.status ?? '…'} open for ${seconds(now - Date.parse(r.at))}`
|
|
120
|
+
: `${r.status} (${seconds(r.ms ?? 0)})`;
|
|
121
|
+
const kind = r.type === 'fetch' || r.type === 'xhr' ? '' : ` ${r.type}`;
|
|
122
|
+
const lines = [`#${r.seq} ${time} ${r.method} ${r.url} → ${result}${kind}`];
|
|
123
|
+
if (bodies && r.sent)
|
|
124
|
+
lines.push(` sent: ${r.sent}`);
|
|
125
|
+
if (bodies && r.got)
|
|
126
|
+
lines.push(` got: ${r.got}`);
|
|
127
|
+
return lines.join('\n');
|
|
128
|
+
}
|
|
129
|
+
/** The records a query asks for, newest last, at most `limit` (the most recent). */
|
|
130
|
+
export function selectRequests(records, q = {}) {
|
|
131
|
+
const match = q.match?.toLowerCase();
|
|
132
|
+
const picked = records.filter((r) => (q.all || !ASSETS.has(r.type)) &&
|
|
133
|
+
(q.after === undefined || r.seq > q.after) &&
|
|
134
|
+
(!match || r.url.toLowerCase().includes(match)));
|
|
135
|
+
return picked.slice(-(q.limit ?? 40));
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=requests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requests.js","sourceRoot":"","sources":["../src/requests.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/standins/builtin/email/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/standins/builtin/email/index.ts"],"names":[],"mappings":"AAiBA,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGjG"}
|