@trusty-squire/mcp 0.8.2-rc.11 → 0.8.2-rc.13
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/bot/agent.d.ts +2 -0
- package/dist/bot/agent.d.ts.map +1 -1
- package/dist/bot/agent.js +286 -132
- package/dist/bot/agent.js.map +1 -1
- package/package.json +1 -1
package/dist/bot/agent.d.ts
CHANGED
|
@@ -200,6 +200,8 @@ export declare function parsePostVerifyStep(raw: string, allowedSelectors?: Read
|
|
|
200
200
|
export declare function isTruncatedCapture(sourceText: string, capturedKey: string): boolean;
|
|
201
201
|
export declare function extractQuotedTokenFromReason(reason: string, pageText: string): string | null;
|
|
202
202
|
export declare function extractAllLabeledTokensFromReason(reason: string, pageText: string): Record<string, string>;
|
|
203
|
+
export declare function hasAnyExtractedCredential(creds: Record<string, string>): boolean;
|
|
204
|
+
export declare function isMultiCredBundle(creds: Record<string, string>): boolean;
|
|
203
205
|
export declare function extractApiKeyFromText(text: string): string | null;
|
|
204
206
|
export declare function pickVerificationLink(links: readonly string[]): string | null;
|
|
205
207
|
export declare class SignupAgent {
|
package/dist/bot/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/bot/agent.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,iBAAiB,EAGjB,cAAc,EACd,kBAAkB,EACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAOhE,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,OAAO,EACb,MAAM,iBAAiB,CAAC;AAMzB,MAAM,WAAW,UAAU;IACzB,YAAY,CAAC,KAAK,EAAE;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE;YAAE,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,aAAa,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACrE,eAAe,EAAE,MAAM,CAAC;KACzB,GAAG,OAAO,CAAC;QACV,OAAO,EAAE,MAAM,CAAC;QAChB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;KACrC,CAAC,CAAC;CACJ;AA6GD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAUjD;AAMD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGlE;AAED,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,MAAM,EAAE,MAAM;CAI3B;
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/bot/agent.ts"],"names":[],"mappings":"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"}
|
package/dist/bot/agent.js
CHANGED
|
@@ -192,8 +192,14 @@ const STUCK_LOOP_FALLBACK_PATHS = [
|
|
|
192
192
|
// describes the same shape.
|
|
193
193
|
const EXISTING_KEY_URL_HINT = /(?:api[-_/]keys?|api[-_/]tokens?|auth[-_/]tokens?|personal[-_/]access[-_/]tokens?|\/keys(?:\b|\/|$)|\/tokens(?:\b|\/|$)|\/settings\/keys\b|\/settings\/tokens\b|#api[-_/]keys\b|#api[-_/]tokens\b)/i;
|
|
194
194
|
const MASKED_KEY_GLYPHS = /(?:•{3,}|\*{3,}|─•|·{3,}|•{3,}|x{6,}|[A-Za-z0-9]{2,4}[•*]{5,})/;
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
// 0.8.2-rc.12 — widened to catch Neon's existing-key list shape
|
|
196
|
+
// (the per-row layout has a "Key name" header + "Created <date>" +
|
|
197
|
+
// "Last used <date|never>" — no glyph, no "existing" word, just the
|
|
198
|
+
// columns of an API-key listing table). The conservative AND with
|
|
199
|
+
// EXISTING_KEY_URL_HINT keeps this from misfiring on marketing copy
|
|
200
|
+
// elsewhere on a non-keys URL.
|
|
201
|
+
const EXISTING_KEY_WORDS = /\b(?:existing\s+(?:api\s+)?(?:key|token)|previously\s+created|created\s+by\b|api\s+keys?\s*\(\d+\)|tokens?\s*\(\d+\)|reveal|copy\s+key|key\s+name\b|last\s+used\b|created(?:\s+\w+){0,3}\s+(?:\d{1,2},?\s+)?\d{4}\b)/i;
|
|
202
|
+
const NO_CREATE_AFFORDANCE_HINT = /\b(?:cannot\s+(?:reveal|extract|read)|values?\s+(?:is\s+)?masked|only\s+shown\s+once|cannot\s+(?:see|view|copy)\s+(?:the\s+)?(?:key|secret|value)|key\s+(?:value|secret)\s+(?:is\s+)?(?:not\s+)?(?:available|recoverable|extractable|shown))\b/i;
|
|
197
203
|
export function detectExistingAccountNoExtract(input) {
|
|
198
204
|
if (!EXISTING_KEY_URL_HINT.test(input.url))
|
|
199
205
|
return false;
|
|
@@ -202,12 +208,38 @@ export function detectExistingAccountNoExtract(input) {
|
|
|
202
208
|
if (NO_CREATE_AFFORDANCE_HINT.test(input.lastPlannerReason)) {
|
|
203
209
|
return true;
|
|
204
210
|
}
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
+
// 0.8.2-rc.12 — three independent positive paths, ANY of which is
|
|
212
|
+
// enough since we already gated on the URL matching an API-keys
|
|
213
|
+
// page (which alone weeds out the marketing-tile false-positives
|
|
214
|
+
// the conservative pre-rc.12 path was protecting against):
|
|
215
|
+
// 1. Mask glyphs in the page (•••, asterisks, ··· — the literal
|
|
216
|
+
// "value is hidden" decoration most vendors use).
|
|
217
|
+
// 2. Two or more existing-key word patterns matched (a key
|
|
218
|
+
// LISTING shape: "Key name" + "Last used" + "Created <date>"
|
|
219
|
+
// is unmistakable when found on a /keys-style URL).
|
|
220
|
+
// 3. Mask glyph PLUS any existing-key word (the original
|
|
221
|
+
// detector — keeps the conservative behavior for vendors
|
|
222
|
+
// whose listing UI uses different column labels).
|
|
223
|
+
const hasMaskGlyph = MASKED_KEY_GLYPHS.test(input.pageText);
|
|
224
|
+
// Tally up to 5 distinct existing-key signals; 2+ is enough.
|
|
225
|
+
const existingKeyMatches = [];
|
|
226
|
+
const allWords = input.pageText.match(new RegExp(EXISTING_KEY_WORDS, "gi"));
|
|
227
|
+
if (allWords !== null) {
|
|
228
|
+
const distinct = new Set();
|
|
229
|
+
for (const m of allWords) {
|
|
230
|
+
distinct.add(m.toLowerCase().replace(/\s+/g, " "));
|
|
231
|
+
if (distinct.size >= 5)
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
existingKeyMatches.push(...distinct);
|
|
235
|
+
}
|
|
236
|
+
if (hasMaskGlyph && existingKeyMatches.length >= 1)
|
|
237
|
+
return true;
|
|
238
|
+
if (existingKeyMatches.length >= 2)
|
|
239
|
+
return true;
|
|
240
|
+
if (hasMaskGlyph && /\bAPI\s+keys?\b/i.test(input.pageText))
|
|
241
|
+
return true;
|
|
242
|
+
return false;
|
|
211
243
|
}
|
|
212
244
|
// Pick the next fallback URL to try from STUCK_LOOP_FALLBACK_PATHS
|
|
213
245
|
// keyed against the origin of the currently-stuck URL. Returns null
|
|
@@ -1559,6 +1591,43 @@ export function extractAllLabeledTokensFromReason(reason, pageText) {
|
|
|
1559
1591
|
function escapeRegex(s) {
|
|
1560
1592
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1561
1593
|
}
|
|
1594
|
+
// Keys that the postVerifyLoop's accumulator stores for housekeeping —
|
|
1595
|
+
// they're NOT extracted credentials and must NOT count as "we found
|
|
1596
|
+
// something" when deciding whether an extract round succeeded.
|
|
1597
|
+
const NON_CREDENTIAL_KEYS = new Set([
|
|
1598
|
+
"api_key_truncated", // truncated stub from extractCredentials Pass 1
|
|
1599
|
+
"password", // signup form metadata (email-verification path)
|
|
1600
|
+
"email", // signup form metadata
|
|
1601
|
+
]);
|
|
1602
|
+
// True iff the credentials Record holds at least one extracted value
|
|
1603
|
+
// (api_key, username, or any labeled multi-cred field). Excludes
|
|
1604
|
+
// metadata + truncated stubs. Used to decide "this extract round
|
|
1605
|
+
// produced something — continue the loop / capture a synthetic extract
|
|
1606
|
+
// round" vs "every tier missed — try the planner-quoted fallback".
|
|
1607
|
+
export function hasAnyExtractedCredential(creds) {
|
|
1608
|
+
for (const key of Object.keys(creds)) {
|
|
1609
|
+
if (NON_CREDENTIAL_KEYS.has(key))
|
|
1610
|
+
continue;
|
|
1611
|
+
return true;
|
|
1612
|
+
}
|
|
1613
|
+
return false;
|
|
1614
|
+
}
|
|
1615
|
+
// True iff the credentials Record contains a multi-credential bundle
|
|
1616
|
+
// — anything beyond the legacy single api_key/username pair. Used by
|
|
1617
|
+
// the post-verify loop's early-exit so a partial multi-cred capture
|
|
1618
|
+
// doesn't return prematurely (Cloudinary's api_key surfaces 4-5
|
|
1619
|
+
// rounds before api_secret; the legacy exit fired the moment api_key
|
|
1620
|
+
// was set, losing cloud_name + api_secret).
|
|
1621
|
+
export function isMultiCredBundle(creds) {
|
|
1622
|
+
for (const key of Object.keys(creds)) {
|
|
1623
|
+
if (NON_CREDENTIAL_KEYS.has(key))
|
|
1624
|
+
continue;
|
|
1625
|
+
if (key === "api_key" || key === "username")
|
|
1626
|
+
continue;
|
|
1627
|
+
return true;
|
|
1628
|
+
}
|
|
1629
|
+
return false;
|
|
1630
|
+
}
|
|
1562
1631
|
export function extractApiKeyFromText(text) {
|
|
1563
1632
|
const prefixed = [
|
|
1564
1633
|
/\bre_[a-zA-Z0-9_]{20,}\b/, // Resend (key body contains underscores)
|
|
@@ -3952,11 +4021,44 @@ ${formatInventory(input.inventory)}`,
|
|
|
3952
4021
|
// contiguous 0..N-1 chain regardless of how many planner re-plans
|
|
3953
4022
|
// happen mid-run.
|
|
3954
4023
|
let capturedRound = 0;
|
|
4024
|
+
// 0.8.2-rc.12 — multi-cred-aware loop exit. Track the number of
|
|
4025
|
+
// distinct credential keys we've accumulated; if we're in a
|
|
4026
|
+
// multi-cred bundle (cloud_name, api_secret, application_id, …)
|
|
4027
|
+
// keep planning past the first api_key surfacing so siblings can
|
|
4028
|
+
// accumulate. Bounded by `roundsSinceLastNewCredential` so a
|
|
4029
|
+
// page that never produces a sibling doesn't loop forever.
|
|
4030
|
+
let lastCredentialKeyCount = Object.keys(credentials).filter((k) => !NON_CREDENTIAL_KEYS.has(k)).length;
|
|
4031
|
+
let roundsSinceLastNewCredential = 0;
|
|
4032
|
+
const MAX_ROUNDS_AWAITING_MORE_CREDENTIALS = 3;
|
|
3955
4033
|
for (let round = 0; round < args.maxRounds; round++) {
|
|
3956
|
-
|
|
4034
|
+
const currentCredentialKeyCount = Object.keys(credentials).filter((k) => !NON_CREDENTIAL_KEYS.has(k)).length;
|
|
4035
|
+
if (currentCredentialKeyCount > lastCredentialKeyCount) {
|
|
4036
|
+
roundsSinceLastNewCredential = 0;
|
|
4037
|
+
lastCredentialKeyCount = currentCredentialKeyCount;
|
|
4038
|
+
}
|
|
4039
|
+
else if (lastCredentialKeyCount > 0) {
|
|
4040
|
+
roundsSinceLastNewCredential += 1;
|
|
4041
|
+
}
|
|
4042
|
+
// Multi-cred services hold the loop open until either the
|
|
4043
|
+
// planner returns `done`, the budget expires, or we've made
|
|
4044
|
+
// no credential progress for MAX_ROUNDS_AWAITING_MORE_CREDENTIALS
|
|
4045
|
+
// consecutive rounds. Single-cred services keep the legacy
|
|
4046
|
+
// behavior of returning the moment api_key surfaces.
|
|
4047
|
+
const inMultiCredMode = isMultiCredBundle(credentials);
|
|
4048
|
+
if (!inMultiCredMode &&
|
|
4049
|
+
(credentials.api_key !== undefined || credentials.username !== undefined)) {
|
|
3957
4050
|
args.steps.push(`Post-verify: credentials found on round ${round}.`);
|
|
3958
4051
|
return credentials;
|
|
3959
4052
|
}
|
|
4053
|
+
if (inMultiCredMode &&
|
|
4054
|
+
roundsSinceLastNewCredential >= MAX_ROUNDS_AWAITING_MORE_CREDENTIALS &&
|
|
4055
|
+
(credentials.api_key !== undefined || credentials.username !== undefined)) {
|
|
4056
|
+
const summary = Object.keys(credentials)
|
|
4057
|
+
.filter((k) => !NON_CREDENTIAL_KEYS.has(k))
|
|
4058
|
+
.join(", ");
|
|
4059
|
+
args.steps.push(`Post-verify: multi-cred bundle stable for ${roundsSinceLastNewCredential} rounds — returning what we have (${summary}).`);
|
|
4060
|
+
return credentials;
|
|
4061
|
+
}
|
|
3960
4062
|
// Settle the page first — the previous round's click may have
|
|
3961
4063
|
// triggered a navigation, and reading a page mid-navigation
|
|
3962
4064
|
// throws "execution context destroyed". waitForFormReady is
|
|
@@ -4269,6 +4371,36 @@ ${formatInventory(input.inventory)}`,
|
|
|
4269
4371
|
// root happens to share /settings with the API-keys page
|
|
4270
4372
|
// doesn't land short of the actual page.
|
|
4271
4373
|
if (stuckFiresAtUrl >= 2) {
|
|
4374
|
+
// 0.8.2-rc.12 — when the bot is ALREADY on a URL that names
|
|
4375
|
+
// an API-keys page (path contains /keys, /tokens, /api-keys,
|
|
4376
|
+
// etc.) AND the page text shows masked-credential markers,
|
|
4377
|
+
// the dashboard is genuinely showing a pre-existing key
|
|
4378
|
+
// we can't unmask (Neon's `ts-7229` is the canonical case —
|
|
4379
|
+
// the value was revealed once at create-time and is gone).
|
|
4380
|
+
// Skip the fallback-URL navigate entirely (it would land
|
|
4381
|
+
// on a 404 for vendors whose api-keys page lives at an
|
|
4382
|
+
// org-scoped URL like `/app/<org>/settings#api-keys`) and
|
|
4383
|
+
// classify as existing_account_no_extract directly.
|
|
4384
|
+
try {
|
|
4385
|
+
const stuckPageText = await this.browser
|
|
4386
|
+
.extractText()
|
|
4387
|
+
.catch(() => "");
|
|
4388
|
+
if (detectExistingAccountNoExtract({
|
|
4389
|
+
url: state.url,
|
|
4390
|
+
pageText: stuckPageText,
|
|
4391
|
+
lastPlannerReason: nextStep.reason,
|
|
4392
|
+
})) {
|
|
4393
|
+
this.lastPostVerifyDoneReason =
|
|
4394
|
+
`[existing_account_no_extract] stuck-loop at ${state.url} on an existing API-keys page with masked credentials; ` +
|
|
4395
|
+
`latest planner reason: ${nextStep.reason}`;
|
|
4396
|
+
args.steps.push(`Post-verify: stuck-loop on an existing-keys page — classified as existing_account_no_extract, breaking out.`);
|
|
4397
|
+
break;
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
catch {
|
|
4401
|
+
// best-effort — fall through to the regular fallback path
|
|
4402
|
+
// if the page-text read failed.
|
|
4403
|
+
}
|
|
4272
4404
|
const fallback = pickStuckLoopFallbackUrl(state.url, triedFallbackUrls);
|
|
4273
4405
|
if (fallback !== null) {
|
|
4274
4406
|
triedFallbackUrls.add(fallback);
|
|
@@ -4288,7 +4420,12 @@ ${formatInventory(input.inventory)}`,
|
|
|
4288
4420
|
prevSignature = null;
|
|
4289
4421
|
prevInventorySize = -1;
|
|
4290
4422
|
hint = undefined;
|
|
4291
|
-
capturedRound
|
|
4423
|
+
// Don't bump capturedRound — captureOnboardingRound above
|
|
4424
|
+
// already wrote a capture for this round (the stuck-loop
|
|
4425
|
+
// detector runs AFTER the capture, so the planner's
|
|
4426
|
+
// observed step IS on disk). Bumping again here would
|
|
4427
|
+
// leave a phantom gap in the chain that verifyCaptureChain
|
|
4428
|
+
// rejects as missing_round.
|
|
4292
4429
|
continue;
|
|
4293
4430
|
}
|
|
4294
4431
|
// Every plausible fallback URL has been tried and we're
|
|
@@ -4371,126 +4508,121 @@ ${formatInventory(input.inventory)}`,
|
|
|
4371
4508
|
hint = undefined;
|
|
4372
4509
|
try {
|
|
4373
4510
|
if (nextStep.kind === "extract") {
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
credentials[k] = labeledAfter[k];
|
|
4434
|
-
args.steps.push(`Post-verify ${round + 1}/${args.maxRounds}: post-reveal DOM-proximity extracted ${newKeys.length} more (${newKeys.join(", ")})`);
|
|
4435
|
-
}
|
|
4436
|
-
else {
|
|
4437
|
-
// Surface ALL labeled candidates we found, so
|
|
4438
|
-
// we can see whether the value is on-page but
|
|
4439
|
-
// mislabeled vs. genuinely not surfaced.
|
|
4440
|
-
const allLabeled = await this.browser.extractLabeledCredentialCandidates();
|
|
4441
|
-
const summary = allLabeled
|
|
4442
|
-
.filter((c) => !c.isMasked)
|
|
4443
|
-
.slice(0, 8)
|
|
4444
|
-
.map((c) => `${c.value.slice(0, 6)}…(${c.value.length}ch)/${c.label ?? "no-label"}`)
|
|
4445
|
-
.join(", ");
|
|
4446
|
-
args.steps.push(`Post-verify ${round + 1}/${args.maxRounds}: post-reveal had ${allLabeled.length} candidates; visible: ${summary}`);
|
|
4447
|
-
}
|
|
4448
|
-
}
|
|
4511
|
+
// 0.8.2-rc.12 — multi-cred preservation + always-on Phase E.
|
|
4512
|
+
//
|
|
4513
|
+
// Pre-rc.12 the extract step was a tower of "if no api_key,
|
|
4514
|
+
// try Phase E; else done." That short-circuit silently lost
|
|
4515
|
+
// cloud_name + api_secret on Cloudinary-class services whose
|
|
4516
|
+
// api_key is plain-visible to the legacy regex extractor —
|
|
4517
|
+
// the legacy path filled credentials.api_key, the if-branch
|
|
4518
|
+
// skipped Phase E entirely, and the loop's top-of-iter exit
|
|
4519
|
+
// returned a partial bundle.
|
|
4520
|
+
//
|
|
4521
|
+
// New shape: run the legacy extractor, Phase E, the reveal
|
|
4522
|
+
// pass, and DOM-proximity UNCONDITIONALLY on every extract
|
|
4523
|
+
// round, merging each into `credentials` first-wins. A later
|
|
4524
|
+
// pass never clobbers a value an earlier pass labeled. This
|
|
4525
|
+
// mirrors the design doc: Phase E is the multi-cred surface;
|
|
4526
|
+
// single-cred is just multi-cred-with-one-key.
|
|
4527
|
+
const [pageText, inputValues] = await Promise.all([
|
|
4528
|
+
this.browser.extractText().catch(() => ""),
|
|
4529
|
+
this.browser.extractAllInputValues().catch(() => []),
|
|
4530
|
+
]);
|
|
4531
|
+
const verifySource = pageText + "\n" + inputValues.join("\n");
|
|
4532
|
+
// Tier 1 — legacy single-cred extractor (api_key by shape).
|
|
4533
|
+
// Merge into the running accumulator instead of overwriting;
|
|
4534
|
+
// a Phase E label captured on a prior round wins over a
|
|
4535
|
+
// later legacy regex hit.
|
|
4536
|
+
const legacy = await this.extractCredentials();
|
|
4537
|
+
for (const [k, v] of Object.entries(legacy)) {
|
|
4538
|
+
if (credentials[k] === undefined)
|
|
4539
|
+
credentials[k] = v;
|
|
4540
|
+
}
|
|
4541
|
+
// Tier 2 — Phase E labeled-token parser over the planner's
|
|
4542
|
+
// reason. Picks up cloud_name='dlq4xgrca' / api_key='4917…'
|
|
4543
|
+
// / application_id='X' / admin_api_key='…' style narrative.
|
|
4544
|
+
const labeled = extractAllLabeledTokensFromReason(nextStep.reason, verifySource);
|
|
4545
|
+
const labeledNewKeys = Object.keys(labeled).filter((k) => credentials[k] === undefined);
|
|
4546
|
+
if (labeledNewKeys.length > 0) {
|
|
4547
|
+
for (const k of labeledNewKeys)
|
|
4548
|
+
credentials[k] = labeled[k];
|
|
4549
|
+
const summary = labeledNewKeys
|
|
4550
|
+
.map((k) => `${k}=${labeled[k].slice(0, 4)}…${labeled[k].slice(-4)}`)
|
|
4551
|
+
.join(", ");
|
|
4552
|
+
args.steps.push(`Post-verify ${round + 1}/${args.maxRounds}: Phase E surfaced ${labeledNewKeys.length} labeled credential(s) (${summary})`);
|
|
4553
|
+
}
|
|
4554
|
+
// Tier 2.5 — reveal-then-extract when the planner explicitly
|
|
4555
|
+
// flagged a masked credential. Fires whether or not we
|
|
4556
|
+
// already have other credentials — Cloudinary's api_secret
|
|
4557
|
+
// sits beside an already-visible api_key in the table.
|
|
4558
|
+
const MASKED_HINT = /\b(?:masked|hidden|bullets?|asterisks?|••+|\*{3,}|reveal|unmask)\b/i;
|
|
4559
|
+
if (MASKED_HINT.test(nextStep.reason)) {
|
|
4560
|
+
try {
|
|
4561
|
+
const revealRes = await this.browser.revealMaskedCredentials();
|
|
4562
|
+
args.steps.push(`Post-verify ${round + 1}/${args.maxRounds}: reveal pass clicked=${revealRes.clicked} diagnostic=[${revealRes.diagnostic.join("; ")}]`);
|
|
4563
|
+
if (revealRes.clicked > 0) {
|
|
4564
|
+
const labeledAfter = await this.extractFromDomProximity();
|
|
4565
|
+
const afterNewKeys = Object.keys(labeledAfter).filter((k) => credentials[k] === undefined);
|
|
4566
|
+
if (afterNewKeys.length > 0) {
|
|
4567
|
+
for (const k of afterNewKeys)
|
|
4568
|
+
credentials[k] = labeledAfter[k];
|
|
4569
|
+
args.steps.push(`Post-verify ${round + 1}/${args.maxRounds}: post-reveal DOM-proximity extracted ${afterNewKeys.length} more (${afterNewKeys.join(", ")})`);
|
|
4449
4570
|
}
|
|
4450
|
-
|
|
4451
|
-
|
|
4571
|
+
else {
|
|
4572
|
+
// Diagnostic: which candidates were seen on the page?
|
|
4573
|
+
// Helps debug "Reveal click landed but the value
|
|
4574
|
+
// didn't appear in proximity to a known label".
|
|
4575
|
+
const allLabeled = await this.browser.extractLabeledCredentialCandidates();
|
|
4576
|
+
const candSummary = allLabeled
|
|
4577
|
+
.filter((c) => !c.isMasked)
|
|
4578
|
+
.slice(0, 8)
|
|
4579
|
+
.map((c) => `${c.value.slice(0, 6)}…(${c.value.length}ch)/${c.label ?? "no-label"}`)
|
|
4580
|
+
.join(", ");
|
|
4581
|
+
args.steps.push(`Post-verify ${round + 1}/${args.maxRounds}: post-reveal had ${allLabeled.length} candidates; visible: ${candSummary}`);
|
|
4452
4582
|
}
|
|
4453
4583
|
}
|
|
4454
|
-
consecutiveFailedExtracts = 0;
|
|
4455
|
-
continue;
|
|
4456
4584
|
}
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
credentials = { ...credentials, api_key: quoted };
|
|
4460
|
-
args.steps.push(`Post-verify ${round + 1}/${args.maxRounds}: extracted token via ` +
|
|
4461
|
-
`planner-quoted fallback (${quoted.slice(0, 4)}…${quoted.slice(-4)})`);
|
|
4462
|
-
consecutiveFailedExtracts = 0;
|
|
4463
|
-
continue;
|
|
4464
|
-
}
|
|
4465
|
-
// Tier 4 — DOM-proximity labeled credential extraction.
|
|
4466
|
-
// Run BEFORE bailing the extract. Walks the visible DOM,
|
|
4467
|
-
// finds credential-shape strings, pairs each with its
|
|
4468
|
-
// nearest credential-label text by Euclidean center
|
|
4469
|
-
// distance. Catches multi-cred pages where the planner
|
|
4470
|
-
// mentioned ONE value but the DOM shows several (the
|
|
4471
|
-
// planner's narrative-style extract reason missed the
|
|
4472
|
-
// sibling labels). Also tries to unmask hidden secrets
|
|
4473
|
-
// first by clicking visible Reveal/Eye/Copy buttons.
|
|
4474
|
-
try {
|
|
4475
|
-
await this.browser.revealMaskedCredentials();
|
|
4476
|
-
}
|
|
4477
|
-
catch {
|
|
4478
|
-
// Best-effort; never block the extract pass on a
|
|
4479
|
-
// reveal-click failure.
|
|
4585
|
+
catch (err) {
|
|
4586
|
+
args.steps.push(`Post-verify ${round + 1}/${args.maxRounds}: reveal pass error (${err instanceof Error ? err.message : String(err)})`);
|
|
4480
4587
|
}
|
|
4588
|
+
}
|
|
4589
|
+
// Tier 3 — DOM-proximity labeled extractor. Walks the
|
|
4590
|
+
// visible DOM, pairs credential-shape strings with their
|
|
4591
|
+
// nearest credential-label text. Catches services whose
|
|
4592
|
+
// planner-reason narrative missed sibling labels but whose
|
|
4593
|
+
// DOM still has them as <td>/<dt> pairs.
|
|
4594
|
+
try {
|
|
4481
4595
|
const labeledFromDom = await this.extractFromDomProximity();
|
|
4482
|
-
const
|
|
4483
|
-
if (
|
|
4484
|
-
for (const k of
|
|
4596
|
+
const domNewKeys = Object.keys(labeledFromDom).filter((k) => credentials[k] === undefined);
|
|
4597
|
+
if (domNewKeys.length > 0) {
|
|
4598
|
+
for (const k of domNewKeys)
|
|
4485
4599
|
credentials[k] = labeledFromDom[k];
|
|
4486
|
-
const summary =
|
|
4487
|
-
.map((k) => {
|
|
4488
|
-
const v = labeledFromDom[k];
|
|
4489
|
-
return `${k}=${v.slice(0, 4)}…${v.slice(-4)}`;
|
|
4490
|
-
})
|
|
4600
|
+
const summary = domNewKeys
|
|
4601
|
+
.map((k) => `${k}=${labeledFromDom[k].slice(0, 4)}…${labeledFromDom[k].slice(-4)}`)
|
|
4491
4602
|
.join(", ");
|
|
4492
|
-
args.steps.push(`Post-verify ${round + 1}/${args.maxRounds}:
|
|
4493
|
-
|
|
4603
|
+
args.steps.push(`Post-verify ${round + 1}/${args.maxRounds}: DOM-proximity surfaced ${domNewKeys.length} more (${summary})`);
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
4606
|
+
catch {
|
|
4607
|
+
// best-effort; never abort an extract pass on DOM-proximity
|
|
4608
|
+
// failure (page mid-navigation etc).
|
|
4609
|
+
}
|
|
4610
|
+
// Anything found across all tiers? hasMultiCredCredentials
|
|
4611
|
+
// also catches non-api_key labels (cloud_name, application_id).
|
|
4612
|
+
if (hasAnyExtractedCredential(credentials)) {
|
|
4613
|
+
consecutiveFailedExtracts = 0;
|
|
4614
|
+
continue;
|
|
4615
|
+
}
|
|
4616
|
+
// True extract failure — every tier missed. Try the legacy
|
|
4617
|
+
// single-value planner-quoted fallback for services whose
|
|
4618
|
+
// planner prose just bare-quotes the value without a known
|
|
4619
|
+
// label vocabulary (Railway UUID-only, IPInfo 14-hex).
|
|
4620
|
+
{
|
|
4621
|
+
const quoted = extractQuotedTokenFromReason(nextStep.reason, verifySource);
|
|
4622
|
+
if (quoted !== null) {
|
|
4623
|
+
credentials.api_key = quoted;
|
|
4624
|
+
args.steps.push(`Post-verify ${round + 1}/${args.maxRounds}: extracted token via ` +
|
|
4625
|
+
`planner-quoted fallback (${quoted.slice(0, 4)}…${quoted.slice(-4)})`);
|
|
4494
4626
|
consecutiveFailedExtracts = 0;
|
|
4495
4627
|
continue;
|
|
4496
4628
|
}
|
|
@@ -4558,9 +4690,6 @@ ${formatInventory(input.inventory)}`,
|
|
|
4558
4690
|
"generate a fresh one — its full value is shown once, on creation.";
|
|
4559
4691
|
}
|
|
4560
4692
|
}
|
|
4561
|
-
else {
|
|
4562
|
-
consecutiveFailedExtracts = 0;
|
|
4563
|
-
}
|
|
4564
4693
|
}
|
|
4565
4694
|
else if (nextStep.kind === "click") {
|
|
4566
4695
|
await this.browser.click(nextStep.selector);
|
|
@@ -4576,13 +4705,23 @@ ${formatInventory(input.inventory)}`,
|
|
|
4576
4705
|
// services without modal-delay returns in <1s. Saves both
|
|
4577
4706
|
// time (no overshoot wait) and correctness (catches the
|
|
4578
4707
|
// modal-render race).
|
|
4708
|
+
// 0.8.2-rc.12 — merge polled extract into the running
|
|
4709
|
+
// credentials accumulator (was previously assigned to a
|
|
4710
|
+
// throwaway `pollExtract` local). On modal-key reveal
|
|
4711
|
+
// flows (OpenRouter, Anthropic, OpenAI) the credential
|
|
4712
|
+
// appears only here, and the legacy assignment was lost
|
|
4713
|
+
// unless the next round's top-of-iter re-read just
|
|
4714
|
+
// happened to find it again — a flaky guarantee.
|
|
4579
4715
|
const credentialDeadline = Date.now() + 8000;
|
|
4580
|
-
let pollExtract = {};
|
|
4581
4716
|
while (Date.now() < credentialDeadline) {
|
|
4582
4717
|
await this.browser.wait(0.5);
|
|
4583
4718
|
try {
|
|
4584
|
-
pollExtract = await this.extractCredentials();
|
|
4585
|
-
|
|
4719
|
+
const pollExtract = await this.extractCredentials();
|
|
4720
|
+
for (const [k, v] of Object.entries(pollExtract)) {
|
|
4721
|
+
if (credentials[k] === undefined)
|
|
4722
|
+
credentials[k] = v;
|
|
4723
|
+
}
|
|
4724
|
+
if (credentials.api_key !== undefined)
|
|
4586
4725
|
break;
|
|
4587
4726
|
}
|
|
4588
4727
|
catch {
|
|
@@ -4667,9 +4806,24 @@ ${formatInventory(input.inventory)}`,
|
|
|
4667
4806
|
}
|
|
4668
4807
|
// Re-extract — but tolerate the page still navigating from the
|
|
4669
4808
|
// step just taken; the next round settles and re-reads.
|
|
4670
|
-
|
|
4809
|
+
// 0.8.2-rc.12 — MERGE into the running accumulator. The pre-
|
|
4810
|
+
// rc.12 unconditional assignment wiped multi-cred fields the
|
|
4811
|
+
// explicit extract round just accumulated (cloud_name, api_secret,
|
|
4812
|
+
// etc.); on the next round's top-of-iter early-exit, only the
|
|
4813
|
+
// legacy single api_key survived.
|
|
4814
|
+
// 0.8.2-rc.12 — count distinct credential keys before re-extract
|
|
4815
|
+
// so the synthetic-extract trigger fires on ANY new key, not just
|
|
4816
|
+
// the legacy api_key / username pair. A cloudinary reveal click
|
|
4817
|
+
// can produce a fresh api_secret while api_key was already set;
|
|
4818
|
+
// the pre-rc.12 trigger silently skipped the synthetic capture
|
|
4819
|
+
// and the synthesizer then rejected on no_extract_step.
|
|
4820
|
+
const credCountBefore = Object.keys(credentials).filter((k) => !NON_CREDENTIAL_KEYS.has(k)).length;
|
|
4671
4821
|
try {
|
|
4672
|
-
|
|
4822
|
+
const reExtract = await this.extractCredentials();
|
|
4823
|
+
for (const [k, v] of Object.entries(reExtract)) {
|
|
4824
|
+
if (credentials[k] === undefined)
|
|
4825
|
+
credentials[k] = v;
|
|
4826
|
+
}
|
|
4673
4827
|
}
|
|
4674
4828
|
catch {
|
|
4675
4829
|
// page mid-navigation — next round's waitForFormReady handles it
|
|
@@ -4687,8 +4841,8 @@ ${formatInventory(input.inventory)}`,
|
|
|
4687
4841
|
// RIGHT NOW (the action just ran, the token row is now visible).
|
|
4688
4842
|
// Best-effort — a capture failure must never block returning the
|
|
4689
4843
|
// credential we already have.
|
|
4690
|
-
const
|
|
4691
|
-
|
|
4844
|
+
const credCountAfter = Object.keys(credentials).filter((k) => !NON_CREDENTIAL_KEYS.has(k)).length;
|
|
4845
|
+
const haveNewCredentials = credCountAfter > credCountBefore;
|
|
4692
4846
|
if (haveNewCredentials && nextStep.kind !== "extract") {
|
|
4693
4847
|
try {
|
|
4694
4848
|
const [postState, postInventory] = await Promise.all([
|