@hasna/testers 0.0.29 → 0.0.31
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 +32 -0
- package/dist/cli/index.js +411 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +379 -1
- package/dist/lib/a11y-audit.d.ts.map +1 -1
- package/dist/lib/api-discovery.d.ts.map +1 -1
- package/dist/lib/browser.d.ts +1 -1
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/discovery.d.ts +11 -1
- package/dist/lib/discovery.d.ts.map +1 -1
- package/dist/lib/dom-mutation.d.ts.map +1 -1
- package/dist/lib/hybrid-runner.d.ts.map +1 -1
- package/dist/lib/junit-export.d.ts.map +1 -1
- package/dist/lib/offline-mode.d.ts.map +1 -1
- package/dist/lib/preview-detect.d.ts +1 -1
- package/dist/lib/preview-detect.d.ts.map +1 -1
- package/dist/lib/prod-debug.d.ts +77 -0
- package/dist/lib/prod-debug.d.ts.map +1 -0
- package/dist/lib/responsive.d.ts.map +1 -1
- package/dist/mcp/index.js +451 -41
- package/dist/sdk/index.d.ts +3 -2
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/server/index.js +90 -4
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -29,6 +29,38 @@ Passing a URL as the first argument will, by default, crawl the site and auto-ge
|
|
|
29
29
|
- `--overall-timeout <ms>` — hard timeout for the whole run (default: 10 minutes; CI safety net).
|
|
30
30
|
- `--github-comment` — post a pass/fail summary as a comment on the current GitHub PR.
|
|
31
31
|
|
|
32
|
+
### Secure Production Debugging
|
|
33
|
+
|
|
34
|
+
Use `prod-debug` when an agent or support engineer needs to inspect a production issue without handling customer passwords, raw cookies, bearer tokens, or OAuth codes:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
testers prod-debug "https://alumia.com/acme/projects/project-123?agent=agent-id" --reason "connector auth error"
|
|
38
|
+
testers prod-debug "req_abc123" --logs --json
|
|
39
|
+
testers prod-debug "https://app.example.com/org/projects/p1" --profile app --json
|
|
40
|
+
testers prod-debug "https://app.example.com/org/projects/p1" --support-url "https://support.example.com/scoped/session" --support-grant support-grant-123
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The command parses the target, redacts sensitive URL parameters, emits safe browser/API/log checks, and blocks user-scoped browser reproduction until the target app provides an audited support browser/session URL or a configured profile that can resolve one. It is app-generic: add a profile in `~/.hasna/testers/config.json` for each production app rather than hardcoding app-specific behavior in the CLI.
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"prodDebug": {
|
|
48
|
+
"apps": {
|
|
49
|
+
"app": {
|
|
50
|
+
"name": "App",
|
|
51
|
+
"origins": ["https://app.example.com", "*.app.example.org"],
|
|
52
|
+
"supportGrantRef": "$APP_SUPPORT_GRANT",
|
|
53
|
+
"supportUrlTemplate": "https://support.example.com/scoped/session?grant={supportGrant}&target={targetUrlEncoded}",
|
|
54
|
+
"piiOrigin": "https://api.app.example.com",
|
|
55
|
+
"logCommand": "app logs --project {project} --session {session} --request {request}"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Credential-bearing profile values can point at environment variables (`$APP_SUPPORT_GRANT`) or the local Hasna secrets vault (`@secrets:division/app/support/grant`). Generated plans redact token, grant, session, key, password, OAuth code, and bearer values before printing or writing output.
|
|
63
|
+
|
|
32
64
|
### Exit Codes
|
|
33
65
|
|
|
34
66
|
| Code | Meaning |
|
package/dist/cli/index.js
CHANGED
|
@@ -13861,7 +13861,8 @@ function loadConfig() {
|
|
|
13861
13861
|
judgeModel: fileConfig.judgeModel,
|
|
13862
13862
|
judgeProvider: fileConfig.judgeProvider,
|
|
13863
13863
|
selfHeal: fileConfig.selfHeal ?? false,
|
|
13864
|
-
conversationsSpace: fileConfig.conversationsSpace
|
|
13864
|
+
conversationsSpace: fileConfig.conversationsSpace,
|
|
13865
|
+
prodDebug: fileConfig.prodDebug
|
|
13865
13866
|
};
|
|
13866
13867
|
const envModel = process.env["TESTERS_MODEL"];
|
|
13867
13868
|
if (envModel) {
|
|
@@ -16223,6 +16224,11 @@ function resolveCredential(value) {
|
|
|
16223
16224
|
}
|
|
16224
16225
|
return value;
|
|
16225
16226
|
}
|
|
16227
|
+
function isCredentialReference(value) {
|
|
16228
|
+
if (!value)
|
|
16229
|
+
return false;
|
|
16230
|
+
return value.startsWith("@secrets:") || value.startsWith("$");
|
|
16231
|
+
}
|
|
16226
16232
|
var init_secrets_resolver = () => {};
|
|
16227
16233
|
|
|
16228
16234
|
// src/lib/persona-auth.ts
|
|
@@ -27744,7 +27750,8 @@ async function runHybridScenario(scenario, options) {
|
|
|
27744
27750
|
createdAt: new Date().toISOString(),
|
|
27745
27751
|
updatedAt: new Date().toISOString(),
|
|
27746
27752
|
lastPassedAt: null,
|
|
27747
|
-
lastPassedUrl: null
|
|
27753
|
+
lastPassedUrl: null,
|
|
27754
|
+
parameters: null
|
|
27748
27755
|
};
|
|
27749
27756
|
try {
|
|
27750
27757
|
const agentResult = await runAgentLoop({
|
|
@@ -27835,7 +27842,7 @@ import chalk6 from "chalk";
|
|
|
27835
27842
|
// package.json
|
|
27836
27843
|
var package_default = {
|
|
27837
27844
|
name: "@hasna/testers",
|
|
27838
|
-
version: "0.0.
|
|
27845
|
+
version: "0.0.31",
|
|
27839
27846
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
27840
27847
|
type: "module",
|
|
27841
27848
|
main: "dist/index.js",
|
|
@@ -27900,7 +27907,7 @@ var package_default = {
|
|
|
27900
27907
|
},
|
|
27901
27908
|
repository: {
|
|
27902
27909
|
type: "git",
|
|
27903
|
-
url: "https://github.com/hasna/
|
|
27910
|
+
url: "git+https://github.com/hasna/testers.git"
|
|
27904
27911
|
},
|
|
27905
27912
|
license: "Apache-2.0",
|
|
27906
27913
|
keywords: [
|
|
@@ -28965,6 +28972,383 @@ function generateLatestReport() {
|
|
|
28965
28972
|
|
|
28966
28973
|
// src/cli/index.tsx
|
|
28967
28974
|
init_costs();
|
|
28975
|
+
|
|
28976
|
+
// src/lib/prod-debug.ts
|
|
28977
|
+
init_secrets_resolver();
|
|
28978
|
+
var UUID_RE = /\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/i;
|
|
28979
|
+
var SENSITIVE_PARAM_RE = /token|secret|key|password|code|state|cookie|session|grant|credential|auth|jwt|access/i;
|
|
28980
|
+
var SENSITIVE_TEXT_RE = /\b(Bearer\s+[A-Za-z0-9._-]{12,}|sk-[A-Za-z0-9]{12,}|pk_[A-Za-z0-9]{12,}|eyJ[A-Za-z0-9._-]{12,})\b/g;
|
|
28981
|
+
var URL_TEXT_RE = /https?:\/\/[^\s"'<>]+/g;
|
|
28982
|
+
function safeUrl(raw) {
|
|
28983
|
+
try {
|
|
28984
|
+
const url = new URL(raw);
|
|
28985
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
28986
|
+
return null;
|
|
28987
|
+
return url;
|
|
28988
|
+
} catch {
|
|
28989
|
+
return null;
|
|
28990
|
+
}
|
|
28991
|
+
}
|
|
28992
|
+
function normalizeOrigin(raw) {
|
|
28993
|
+
const url = safeUrl(raw);
|
|
28994
|
+
if (url)
|
|
28995
|
+
return url.origin;
|
|
28996
|
+
const hostUrl = safeUrl(`https://${raw}`);
|
|
28997
|
+
return hostUrl?.origin ?? null;
|
|
28998
|
+
}
|
|
28999
|
+
function redactProdDebugText(value) {
|
|
29000
|
+
return value.replace(URL_TEXT_RE, (match) => {
|
|
29001
|
+
const url = safeUrl(match);
|
|
29002
|
+
return url ? redactUrl(url) : match;
|
|
29003
|
+
}).replace(SENSITIVE_TEXT_RE, (match) => {
|
|
29004
|
+
if (match.startsWith("Bearer "))
|
|
29005
|
+
return "Bearer [redacted]";
|
|
29006
|
+
return "[redacted]";
|
|
29007
|
+
});
|
|
29008
|
+
}
|
|
29009
|
+
function redactUrl(url) {
|
|
29010
|
+
const clone = new URL(url.toString());
|
|
29011
|
+
for (const key of Array.from(clone.searchParams.keys())) {
|
|
29012
|
+
if (SENSITIVE_PARAM_RE.test(key)) {
|
|
29013
|
+
clone.searchParams.set(key, "[redacted]");
|
|
29014
|
+
}
|
|
29015
|
+
}
|
|
29016
|
+
return clone.toString();
|
|
29017
|
+
}
|
|
29018
|
+
function redactUrlString(value) {
|
|
29019
|
+
const url = safeUrl(value);
|
|
29020
|
+
return url ? redactUrl(url) : redactProdDebugText(value);
|
|
29021
|
+
}
|
|
29022
|
+
function parseProdDebugTarget(target) {
|
|
29023
|
+
const input = target.trim();
|
|
29024
|
+
const url = safeUrl(input);
|
|
29025
|
+
if (!url) {
|
|
29026
|
+
const id = (input.match(UUID_RE)?.[0] ?? input) || null;
|
|
29027
|
+
return {
|
|
29028
|
+
url: null,
|
|
29029
|
+
origin: null,
|
|
29030
|
+
orgSlug: null,
|
|
29031
|
+
projectRef: null,
|
|
29032
|
+
sessionId: null,
|
|
29033
|
+
agentId: null,
|
|
29034
|
+
requestId: input.startsWith("req_") ? input : null,
|
|
29035
|
+
rawId: id
|
|
29036
|
+
};
|
|
29037
|
+
}
|
|
29038
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
29039
|
+
const projectsIndex = parts.indexOf("projects");
|
|
29040
|
+
const sessionsIndex = parts.indexOf("sessions");
|
|
29041
|
+
const orgSlug = projectsIndex > 0 ? parts[0] ?? null : null;
|
|
29042
|
+
const projectRef = projectsIndex >= 0 ? parts[projectsIndex + 1] ?? null : null;
|
|
29043
|
+
const sessionId = url.searchParams.get("session") ?? (sessionsIndex >= 0 ? parts[sessionsIndex + 1] ?? null : null);
|
|
29044
|
+
return {
|
|
29045
|
+
url: redactUrl(url),
|
|
29046
|
+
origin: url.origin,
|
|
29047
|
+
orgSlug,
|
|
29048
|
+
projectRef,
|
|
29049
|
+
sessionId,
|
|
29050
|
+
agentId: url.searchParams.get("agent"),
|
|
29051
|
+
requestId: url.searchParams.get("requestId") ?? url.searchParams.get("request_id"),
|
|
29052
|
+
rawId: input.match(UUID_RE)?.[0] ?? null
|
|
29053
|
+
};
|
|
29054
|
+
}
|
|
29055
|
+
function boundedTtl(value) {
|
|
29056
|
+
if (!Number.isFinite(value))
|
|
29057
|
+
return 15;
|
|
29058
|
+
return Math.min(Math.max(Math.round(value ?? 15), 1), 60);
|
|
29059
|
+
}
|
|
29060
|
+
function makeCommand(command) {
|
|
29061
|
+
return command.replace(/\s+/g, " ").trim();
|
|
29062
|
+
}
|
|
29063
|
+
function hostnameFromOrigin(origin) {
|
|
29064
|
+
if (!origin)
|
|
29065
|
+
return null;
|
|
29066
|
+
return safeUrl(origin)?.hostname ?? null;
|
|
29067
|
+
}
|
|
29068
|
+
function originMatches(pattern, origin) {
|
|
29069
|
+
if (!origin)
|
|
29070
|
+
return false;
|
|
29071
|
+
const normalizedPattern = normalizeOrigin(pattern);
|
|
29072
|
+
const normalizedOrigin = normalizeOrigin(origin);
|
|
29073
|
+
if (!normalizedOrigin)
|
|
29074
|
+
return false;
|
|
29075
|
+
if (normalizedPattern === normalizedOrigin)
|
|
29076
|
+
return true;
|
|
29077
|
+
const targetHost = hostnameFromOrigin(normalizedOrigin);
|
|
29078
|
+
const patternHost = normalizedPattern ? hostnameFromOrigin(normalizedPattern) : pattern.replace(/^https?:\/\//, "");
|
|
29079
|
+
if (!targetHost || !patternHost)
|
|
29080
|
+
return false;
|
|
29081
|
+
if (patternHost.startsWith("*.")) {
|
|
29082
|
+
const suffix = patternHost.slice(1);
|
|
29083
|
+
return targetHost.endsWith(suffix);
|
|
29084
|
+
}
|
|
29085
|
+
return targetHost === patternHost;
|
|
29086
|
+
}
|
|
29087
|
+
function resolveProfile(input, target, config) {
|
|
29088
|
+
const apps = config?.apps ?? {};
|
|
29089
|
+
const explicitKey = input.profile?.trim() || input.app?.trim() || config?.defaultProfile;
|
|
29090
|
+
if (explicitKey && apps[explicitKey]) {
|
|
29091
|
+
return {
|
|
29092
|
+
key: explicitKey,
|
|
29093
|
+
profile: apps[explicitKey],
|
|
29094
|
+
matchedOrigin: target.origin
|
|
29095
|
+
};
|
|
29096
|
+
}
|
|
29097
|
+
for (const [key, profile] of Object.entries(apps)) {
|
|
29098
|
+
const match = profile.origins?.find((origin) => originMatches(origin, target.origin));
|
|
29099
|
+
if (match) {
|
|
29100
|
+
return { key, profile, matchedOrigin: match };
|
|
29101
|
+
}
|
|
29102
|
+
}
|
|
29103
|
+
return { key: null, profile: null, matchedOrigin: null };
|
|
29104
|
+
}
|
|
29105
|
+
function firstResolvedCredential(...values) {
|
|
29106
|
+
for (const value of values) {
|
|
29107
|
+
if (!value?.trim())
|
|
29108
|
+
continue;
|
|
29109
|
+
const resolved = resolveCredential(value);
|
|
29110
|
+
if (resolved)
|
|
29111
|
+
return resolved;
|
|
29112
|
+
}
|
|
29113
|
+
return null;
|
|
29114
|
+
}
|
|
29115
|
+
function displayCredential(value, source) {
|
|
29116
|
+
if (!value)
|
|
29117
|
+
return null;
|
|
29118
|
+
if (source && isCredentialReference(source))
|
|
29119
|
+
return "[configured]";
|
|
29120
|
+
return redactProdDebugText(value);
|
|
29121
|
+
}
|
|
29122
|
+
function replacementValues(target, input, supportGrant) {
|
|
29123
|
+
const values = {
|
|
29124
|
+
targetUrl: target.url ?? input.target,
|
|
29125
|
+
origin: target.origin ?? "",
|
|
29126
|
+
org: target.orgSlug ?? "",
|
|
29127
|
+
project: target.projectRef ?? "",
|
|
29128
|
+
session: target.sessionId ?? "",
|
|
29129
|
+
agent: target.agentId ?? "",
|
|
29130
|
+
request: target.requestId ?? "",
|
|
29131
|
+
rawId: target.rawId ?? "",
|
|
29132
|
+
reason: input.reason ?? "",
|
|
29133
|
+
supportGrant: supportGrant ?? ""
|
|
29134
|
+
};
|
|
29135
|
+
for (const [key, value] of Object.entries({ ...values })) {
|
|
29136
|
+
values[`${key}Encoded`] = encodeURIComponent(value);
|
|
29137
|
+
}
|
|
29138
|
+
return values;
|
|
29139
|
+
}
|
|
29140
|
+
function renderTemplate(template, values) {
|
|
29141
|
+
return template.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, key) => values[key] ?? "");
|
|
29142
|
+
}
|
|
29143
|
+
function resolveSupportGrant(input, profile) {
|
|
29144
|
+
if (input.supportGrantId?.trim()) {
|
|
29145
|
+
return {
|
|
29146
|
+
value: input.supportGrantId.trim(),
|
|
29147
|
+
display: displayCredential(input.supportGrantId.trim()),
|
|
29148
|
+
source: "input"
|
|
29149
|
+
};
|
|
29150
|
+
}
|
|
29151
|
+
const source = profile?.supportGrantRef ?? profile?.supportGrantId ?? null;
|
|
29152
|
+
const value = firstResolvedCredential(profile?.supportGrantRef, profile?.supportGrantId);
|
|
29153
|
+
return { value, display: displayCredential(value, source ?? undefined), source };
|
|
29154
|
+
}
|
|
29155
|
+
function resolveSupportUrl(input, target, profile, supportGrant) {
|
|
29156
|
+
if (input.supportUrl?.trim())
|
|
29157
|
+
return input.supportUrl.trim();
|
|
29158
|
+
const direct = firstResolvedCredential(profile?.supportUrlRef, profile?.supportUrl);
|
|
29159
|
+
if (direct)
|
|
29160
|
+
return direct;
|
|
29161
|
+
if (profile?.supportUrlTemplate) {
|
|
29162
|
+
const rendered = renderTemplate(profile.supportUrlTemplate, replacementValues(target, input, supportGrant)).trim();
|
|
29163
|
+
return rendered || null;
|
|
29164
|
+
}
|
|
29165
|
+
return null;
|
|
29166
|
+
}
|
|
29167
|
+
function resolvePiiOrigin(profile, target) {
|
|
29168
|
+
if (!profile?.piiOrigin)
|
|
29169
|
+
return target.origin;
|
|
29170
|
+
return redactUrlString(renderTemplate(profile.piiOrigin, replacementValues(target, { target: target.url ?? "" }, null)));
|
|
29171
|
+
}
|
|
29172
|
+
function resolveSupportRunTarget(supportUrl, input, target) {
|
|
29173
|
+
if (supportUrl)
|
|
29174
|
+
return redactUrlString(supportUrl);
|
|
29175
|
+
return target.url ?? target.origin ?? redactProdDebugText(input.target);
|
|
29176
|
+
}
|
|
29177
|
+
function supportScenarioDescription(reason) {
|
|
29178
|
+
return `Prod debug: ${reason}. Reproduce the user-visible issue, capture console and network errors, and do not enter secrets.`;
|
|
29179
|
+
}
|
|
29180
|
+
function configuredMissing(profile, supportUrl, supportGrant, includeLogs) {
|
|
29181
|
+
const missing = [];
|
|
29182
|
+
if (!profile) {
|
|
29183
|
+
missing.push("optional: add prodDebug.apps.<profile>.origins to match this app automatically");
|
|
29184
|
+
}
|
|
29185
|
+
if (!supportUrl) {
|
|
29186
|
+
missing.push("supportUrl/supportUrlRef/supportUrlTemplate for scoped browser debugging");
|
|
29187
|
+
}
|
|
29188
|
+
if (!supportGrant) {
|
|
29189
|
+
missing.push("supportGrantId/supportGrantRef for auditable support access");
|
|
29190
|
+
}
|
|
29191
|
+
if (includeLogs && !profile?.logCommand) {
|
|
29192
|
+
missing.push("logCommand for sanitized app/provider log lookup");
|
|
29193
|
+
}
|
|
29194
|
+
return missing;
|
|
29195
|
+
}
|
|
29196
|
+
function createProdDebugPlan(input, config) {
|
|
29197
|
+
const target = parseProdDebugTarget(input.target);
|
|
29198
|
+
const browserRequested = input.includeBrowser !== false;
|
|
29199
|
+
const resolvedProfile = resolveProfile(input, target, config);
|
|
29200
|
+
const supportGrant = resolveSupportGrant(input, resolvedProfile.profile);
|
|
29201
|
+
const supportUrl = resolveSupportUrl(input, target, resolvedProfile.profile, supportGrant.value);
|
|
29202
|
+
const supportBrowserReady = Boolean(supportUrl);
|
|
29203
|
+
const app = input.app?.trim() || resolvedProfile.profile?.name || resolvedProfile.key || (target.origin ? new URL(target.origin).hostname : "app");
|
|
29204
|
+
const reason = input.reason?.trim() || "production debug requested";
|
|
29205
|
+
const actor = input.actor?.trim() || process.env["USER"] || "agent";
|
|
29206
|
+
const ttlMinutes = boundedTtl(input.ttlMinutes);
|
|
29207
|
+
const piiOrigin = resolvePiiOrigin(resolvedProfile.profile, target);
|
|
29208
|
+
const logCommand = resolvedProfile.profile?.logCommand ? redactUrlString(renderTemplate(resolvedProfile.profile.logCommand, replacementValues(target, { ...input, reason }, supportGrant.value))) : null;
|
|
29209
|
+
const safety = [
|
|
29210
|
+
"read-only by default",
|
|
29211
|
+
"no customer passwords or raw cookies",
|
|
29212
|
+
"redact tokens, OAuth codes, session values, support grants, and secrets",
|
|
29213
|
+
"verify org/user/session scope before reading data",
|
|
29214
|
+
"require explicit approval for production writes",
|
|
29215
|
+
`support access TTL capped at ${ttlMinutes} minutes`
|
|
29216
|
+
];
|
|
29217
|
+
const checks = [];
|
|
29218
|
+
const blocked = [];
|
|
29219
|
+
if (target.url) {
|
|
29220
|
+
checks.push({
|
|
29221
|
+
id: "public-route-smoke",
|
|
29222
|
+
status: "ready",
|
|
29223
|
+
description: "Open the supplied production URL and capture console/network errors without credentials.",
|
|
29224
|
+
command: makeCommand(`testers scan all ${JSON.stringify(target.url)} --json`)
|
|
29225
|
+
});
|
|
29226
|
+
}
|
|
29227
|
+
checks.push({
|
|
29228
|
+
id: "pii-redaction-scan",
|
|
29229
|
+
status: piiOrigin ? "ready" : "blocked",
|
|
29230
|
+
description: "Scan public/API responses for accidental sensitive data leakage.",
|
|
29231
|
+
command: piiOrigin ? makeCommand(`testers scan pii ${JSON.stringify(piiOrigin)} --json`) : undefined,
|
|
29232
|
+
reason: piiOrigin ? undefined : "Need a URL origin or prodDebug app profile piiOrigin to run the PII scan."
|
|
29233
|
+
});
|
|
29234
|
+
if (browserRequested) {
|
|
29235
|
+
if (supportBrowserReady) {
|
|
29236
|
+
checks.push({
|
|
29237
|
+
id: "support-browser-repro",
|
|
29238
|
+
status: "ready",
|
|
29239
|
+
description: "Use an audited support browser/session URL to reproduce the user-visible issue.",
|
|
29240
|
+
command: makeCommand(`testers run ${JSON.stringify(resolveSupportRunTarget(supportUrl, input, target))} ${JSON.stringify(supportScenarioDescription(reason))} --headed --json --overall-timeout 600000`)
|
|
29241
|
+
});
|
|
29242
|
+
} else {
|
|
29243
|
+
const reasonText = supportGrant.value ? "An audited support grant was supplied, but open-testers still needs supportUrl/supportUrlRef/supportUrlTemplate or an app adapter to open a scoped browser session." : "No audited support browser/session grant was supplied. Do not use customer passwords, copied cookies, bearer tokens, or magic links.";
|
|
29244
|
+
blocked.push(reasonText);
|
|
29245
|
+
checks.push({
|
|
29246
|
+
id: "support-browser-repro",
|
|
29247
|
+
status: "blocked",
|
|
29248
|
+
description: "Browser reproduction as the target user requires a short-lived audited support session.",
|
|
29249
|
+
reason: reasonText
|
|
29250
|
+
});
|
|
29251
|
+
}
|
|
29252
|
+
}
|
|
29253
|
+
if (input.includeLogs) {
|
|
29254
|
+
if (logCommand) {
|
|
29255
|
+
checks.push({
|
|
29256
|
+
id: "log-timeline",
|
|
29257
|
+
status: "ready",
|
|
29258
|
+
description: "Read sanitized app/provider logs by request ID, session ID, project ID, or support access ID.",
|
|
29259
|
+
command: makeCommand(logCommand)
|
|
29260
|
+
});
|
|
29261
|
+
} else {
|
|
29262
|
+
checks.push({
|
|
29263
|
+
id: "log-timeline",
|
|
29264
|
+
status: "blocked",
|
|
29265
|
+
description: "Read sanitized app/provider logs by request ID, session ID, project ID, or support access ID.",
|
|
29266
|
+
reason: "Configure prodDebug.apps.<profile>.logCommand or use an app-specific log MCP. Do not paste raw provider logs with headers/secrets."
|
|
29267
|
+
});
|
|
29268
|
+
}
|
|
29269
|
+
}
|
|
29270
|
+
if (input.allowWrites) {
|
|
29271
|
+
blocked.push("Production writes are not part of prod-debug. Require a separate explicit approval and app-specific write tool.");
|
|
29272
|
+
}
|
|
29273
|
+
return {
|
|
29274
|
+
target,
|
|
29275
|
+
app,
|
|
29276
|
+
actor,
|
|
29277
|
+
reason,
|
|
29278
|
+
ttlMinutes,
|
|
29279
|
+
setup: {
|
|
29280
|
+
profile: resolvedProfile.key,
|
|
29281
|
+
matchedOrigin: resolvedProfile.matchedOrigin,
|
|
29282
|
+
configured: {
|
|
29283
|
+
supportUrl: Boolean(supportUrl),
|
|
29284
|
+
supportGrant: Boolean(supportGrant.value),
|
|
29285
|
+
piiOrigin: Boolean(piiOrigin),
|
|
29286
|
+
logCommand: Boolean(logCommand)
|
|
29287
|
+
},
|
|
29288
|
+
missing: configuredMissing(resolvedProfile.profile, supportUrl, supportGrant.value, Boolean(input.includeLogs))
|
|
29289
|
+
},
|
|
29290
|
+
supportAccess: {
|
|
29291
|
+
required: browserRequested,
|
|
29292
|
+
grantId: supportGrant.display,
|
|
29293
|
+
browserReady: supportBrowserReady,
|
|
29294
|
+
note: supportBrowserReady ? "Use the provided audited support access; never print token/cookie values." : "Configure an audited support-browser/session URL, URL ref, or template before user-scoped browser debugging."
|
|
29295
|
+
},
|
|
29296
|
+
safety,
|
|
29297
|
+
checks,
|
|
29298
|
+
blocked
|
|
29299
|
+
};
|
|
29300
|
+
}
|
|
29301
|
+
function formatProdDebugPlan(plan) {
|
|
29302
|
+
const lines = [];
|
|
29303
|
+
lines.push(`Prod debug plan for ${plan.app}`);
|
|
29304
|
+
lines.push("");
|
|
29305
|
+
lines.push("Target");
|
|
29306
|
+
lines.push(`- url: ${plan.target.url ?? "(none)"}`);
|
|
29307
|
+
lines.push(`- org: ${plan.target.orgSlug ?? "(unknown)"}`);
|
|
29308
|
+
lines.push(`- project: ${plan.target.projectRef ?? "(unknown)"}`);
|
|
29309
|
+
lines.push(`- session: ${plan.target.sessionId ?? "(unknown)"}`);
|
|
29310
|
+
lines.push(`- agent: ${plan.target.agentId ?? "(unknown)"}`);
|
|
29311
|
+
lines.push(`- request: ${plan.target.requestId ?? "(unknown)"}`);
|
|
29312
|
+
lines.push("");
|
|
29313
|
+
lines.push("Setup");
|
|
29314
|
+
lines.push(`- profile: ${plan.setup.profile ?? "(none)"}`);
|
|
29315
|
+
lines.push(`- matched origin: ${plan.setup.matchedOrigin ?? "(none)"}`);
|
|
29316
|
+
if (plan.setup.missing.length > 0) {
|
|
29317
|
+
for (const item of plan.setup.missing)
|
|
29318
|
+
lines.push(`- missing: ${item}`);
|
|
29319
|
+
}
|
|
29320
|
+
lines.push("");
|
|
29321
|
+
lines.push("Support access");
|
|
29322
|
+
lines.push(`- actor: ${plan.actor}`);
|
|
29323
|
+
lines.push(`- reason: ${plan.reason}`);
|
|
29324
|
+
lines.push(`- ttl: ${plan.ttlMinutes} minutes`);
|
|
29325
|
+
lines.push(`- grant: ${plan.supportAccess.grantId ?? "(none)"}`);
|
|
29326
|
+
lines.push(`- browser ready: ${plan.supportAccess.browserReady ? "yes" : "no"}`);
|
|
29327
|
+
lines.push(`- note: ${plan.supportAccess.note}`);
|
|
29328
|
+
lines.push("");
|
|
29329
|
+
lines.push("Checks");
|
|
29330
|
+
for (const check of plan.checks) {
|
|
29331
|
+
lines.push(`- ${check.id}: ${check.status} - ${check.description}`);
|
|
29332
|
+
if (check.command)
|
|
29333
|
+
lines.push(` command: ${check.command}`);
|
|
29334
|
+
if (check.reason)
|
|
29335
|
+
lines.push(` blocked: ${check.reason}`);
|
|
29336
|
+
}
|
|
29337
|
+
if (plan.blocked.length > 0) {
|
|
29338
|
+
lines.push("");
|
|
29339
|
+
lines.push("Blocked");
|
|
29340
|
+
for (const item of plan.blocked)
|
|
29341
|
+
lines.push(`- ${item}`);
|
|
29342
|
+
}
|
|
29343
|
+
lines.push("");
|
|
29344
|
+
lines.push("Safety");
|
|
29345
|
+
for (const item of plan.safety)
|
|
29346
|
+
lines.push(`- ${item}`);
|
|
29347
|
+
return lines.join(`
|
|
29348
|
+
`);
|
|
29349
|
+
}
|
|
29350
|
+
|
|
29351
|
+
// src/cli/index.tsx
|
|
28968
29352
|
init_personas();
|
|
28969
29353
|
init_api_checks();
|
|
28970
29354
|
|
|
@@ -29737,6 +30121,29 @@ function logError(...args) {
|
|
|
29737
30121
|
console.error(...args);
|
|
29738
30122
|
}
|
|
29739
30123
|
program2.name("testers").version(package_default.version).description("AI-powered browser testing CLI").option("-q, --quiet", "Suppress all output", false).option("--no-color", "Disable color output");
|
|
30124
|
+
program2.command("prod-debug <target>").description("Create a safe production debug plan for a URL/session/request without leaking secrets").option("--app <name>", "App name for reporting").option("--profile <name>", "prodDebug app profile from testers config").option("--actor <name>", "Operator/agent identity for support audit context").option("--reason <text>", "Debug reason or support context").option("--support-url <url>", "Audited support browser/session URL minted by the target app").option("--support-grant <id>", "Audited support access grant ID").option("--ttl <minutes>", "Support access TTL in minutes, capped at 60", "15").option("--no-browser", "Do not include user-scoped browser reproduction").option("--logs", "Include log timeline adapter requirement", false).option("--allow-writes", "Document that a separate explicit approval is required for writes", false).option("--json", "Output JSON", false).option("-o, --output <filepath>", "Write plan to file").action((target, opts) => {
|
|
30125
|
+
const config = loadConfig();
|
|
30126
|
+
const plan = createProdDebugPlan({
|
|
30127
|
+
target,
|
|
30128
|
+
app: opts.app,
|
|
30129
|
+
profile: opts.profile,
|
|
30130
|
+
actor: opts.actor,
|
|
30131
|
+
reason: opts.reason,
|
|
30132
|
+
supportUrl: opts.supportUrl,
|
|
30133
|
+
supportGrantId: opts.supportGrant,
|
|
30134
|
+
ttlMinutes: parseInt(opts.ttl, 10),
|
|
30135
|
+
includeBrowser: opts.browser,
|
|
30136
|
+
includeLogs: opts.logs,
|
|
30137
|
+
allowWrites: opts.allowWrites
|
|
30138
|
+
}, config.prodDebug);
|
|
30139
|
+
const output = opts.json ? JSON.stringify(plan, null, 2) : formatProdDebugPlan(plan);
|
|
30140
|
+
if (opts.output) {
|
|
30141
|
+
writeFileSync4(resolve(opts.output), output + `
|
|
30142
|
+
`);
|
|
30143
|
+
} else {
|
|
30144
|
+
log(output);
|
|
30145
|
+
}
|
|
30146
|
+
});
|
|
29740
30147
|
var CONFIG_DIR5 = getTestersDir();
|
|
29741
30148
|
var CONFIG_PATH3 = join16(CONFIG_DIR5, "config.json");
|
|
29742
30149
|
function getActiveProject() {
|
package/dist/index.d.ts
CHANGED
|
@@ -39,5 +39,7 @@ export type { CaptureResult } from "./lib/screenshotter.js";
|
|
|
39
39
|
export { resolveCredential, isCredentialReference } from "./lib/secrets-resolver.js";
|
|
40
40
|
export { ensurePersonaAuthenticated, loginWithAuthConfig } from "./lib/persona-auth.js";
|
|
41
41
|
export type { LoginResult } from "./lib/persona-auth.js";
|
|
42
|
+
export { createProdDebugPlan, formatProdDebugPlan, parseProdDebugTarget, redactProdDebugText, } from "./lib/prod-debug.js";
|
|
43
|
+
export type { ProdDebugAppProfile, ProdDebugCheck, ProdDebugConfig, ProdDebugIdentifiers, ProdDebugInput, ProdDebugPlan, } from "./lib/prod-debug.js";
|
|
42
44
|
export { generateGitHubActionsWorkflow, formatPRComment, postGitHubComment, resolvePullRequestNumber, } from "./lib/ci.js";
|
|
43
45
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,gBAAgB,EAChB,SAAS,EACT,YAAY,EACZ,WAAW,EACX,aAAa,EACb,UAAU,EACV,QAAQ,EACR,WAAW,EACX,MAAM,EACN,SAAS,EACT,aAAa,EACb,OAAO,EACP,KAAK,EACL,QAAQ,EACR,GAAG,EACH,MAAM,EACN,UAAU,EACV,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,SAAS,EACT,WAAW,EACX,QAAQ,EACR,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,OAAO,EACP,IAAI,EACJ,eAAe,EACf,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,SAAS,EACT,cAAc,EACd,YAAY,EACZ,eAAe,EACf,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,WAAW,GACZ,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,WAAW,EACX,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,GAAG,EACH,IAAI,EACJ,SAAS,GACV,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,cAAc,EACd,WAAW,EACX,oBAAoB,EACpB,aAAa,EACb,cAAc,EACd,cAAc,GACf,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,SAAS,EACT,MAAM,EACN,QAAQ,EACR,SAAS,EACT,SAAS,GACV,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,YAAY,EACZ,SAAS,EACT,WAAW,EACX,YAAY,EACZ,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,aAAa,GACd,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,aAAa,EACb,QAAQ,EACR,cAAc,EACd,UAAU,GACX,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,cAAc,EACd,WAAW,EACX,aAAa,EACb,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,yBAAyB,EACzB,eAAe,EACf,UAAU,EACV,OAAO,EACP,SAAS,EACT,UAAU,GACX,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,UAAU,EACV,YAAY,IAAI,kBAAkB,EAClC,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,aAAa,EACb,OAAO,EACP,YAAY,EACZ,WAAW,EACX,cAAc,GACf,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,GAClB,MAAM,6BAA6B,CAAC;AAErC,OAAO,EACL,aAAa,EACb,OAAO,EACP,gBAAgB,EAChB,gBAAgB,EAChB,SAAS,GACV,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,aAAa,GACd,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,iBAAiB,EACjB,QAAQ,EACR,WAAW,EACX,aAAa,EACb,UAAU,GACX,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAE7E,OAAO,EACL,cAAc,EACd,UAAU,EACV,aAAa,EACb,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,cAAc,EACd,SAAS,EACT,mBAAmB,EACnB,eAAe,EACf,YAAY,GACb,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACL,SAAS,EACT,SAAS,EACT,cAAc,EACd,WAAW,EACX,cAAc,GACf,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,EACL,WAAW,EACX,eAAe,EACf,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EACL,QAAQ,EACR,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE9D,OAAO,EACL,QAAQ,EACR,kBAAkB,EAClB,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE9D,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,GACd,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,cAAc,EACd,WAAW,EACX,mBAAmB,EACnB,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEhE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EACL,aAAa,EACb,UAAU,EACV,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,WAAW,GACZ,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEjE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AACzE,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AACrF,OAAO,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACxF,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,EACL,6BAA6B,EAC7B,eAAe,EACf,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|