@qulib/core 0.4.2 → 0.4.3

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.
Files changed (111) hide show
  1. package/README.md +34 -8
  2. package/dist/analyze.d.ts.map +1 -1
  3. package/dist/analyze.js +84 -5
  4. package/dist/cli/auth-login-run.d.ts.map +1 -1
  5. package/dist/cli/auth-login-run.js +26 -2
  6. package/dist/cli/index.js +9 -6
  7. package/dist/index.d.ts +6 -5
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +5 -5
  10. package/dist/phases/observe.js +2 -2
  11. package/dist/phases/think.js +1 -1
  12. package/dist/telemetry/telemetry.interface.d.ts +1 -1
  13. package/dist/telemetry/telemetry.interface.d.ts.map +1 -1
  14. package/dist/tools/apply-auth.d.ts +4 -0
  15. package/dist/tools/apply-auth.d.ts.map +1 -0
  16. package/dist/tools/apply-auth.js +35 -0
  17. package/dist/tools/auth/apply.d.ts +4 -0
  18. package/dist/tools/auth/apply.d.ts.map +1 -0
  19. package/dist/tools/auth/apply.js +35 -0
  20. package/dist/tools/auth/block-gap.d.ts +9 -0
  21. package/dist/tools/auth/block-gap.d.ts.map +1 -0
  22. package/dist/tools/auth/block-gap.js +52 -0
  23. package/dist/tools/auth/custom-providers.d.ts +15 -0
  24. package/dist/tools/auth/custom-providers.d.ts.map +1 -0
  25. package/dist/tools/auth/custom-providers.js +62 -0
  26. package/dist/tools/auth/detect.d.ts +23 -0
  27. package/dist/tools/auth/detect.d.ts.map +1 -0
  28. package/dist/tools/auth/detect.js +526 -0
  29. package/dist/tools/auth/detector.d.ts +23 -0
  30. package/dist/tools/auth/detector.d.ts.map +1 -0
  31. package/dist/tools/auth/detector.js +526 -0
  32. package/dist/tools/auth/explore.d.ts +4 -0
  33. package/dist/tools/auth/explore.d.ts.map +1 -0
  34. package/dist/tools/auth/explore.js +346 -0
  35. package/dist/tools/auth/explorer.d.ts +4 -0
  36. package/dist/tools/auth/explorer.d.ts.map +1 -0
  37. package/dist/tools/auth/explorer.js +346 -0
  38. package/dist/tools/auth/gaps.d.ts +9 -0
  39. package/dist/tools/auth/gaps.d.ts.map +1 -0
  40. package/dist/tools/auth/gaps.js +52 -0
  41. package/dist/tools/auth/oauth-providers.d.ts +7 -0
  42. package/dist/tools/auth/oauth-providers.d.ts.map +1 -0
  43. package/dist/tools/auth/oauth-providers.js +21 -0
  44. package/dist/tools/auth/providers.d.ts +7 -0
  45. package/dist/tools/auth/providers.d.ts.map +1 -0
  46. package/dist/tools/auth/providers.js +21 -0
  47. package/dist/tools/auth/surface-analyzer.d.ts +4 -0
  48. package/dist/tools/auth/surface-analyzer.d.ts.map +1 -0
  49. package/dist/tools/auth/surface-analyzer.js +170 -0
  50. package/dist/tools/auth/surface.d.ts +4 -0
  51. package/dist/tools/auth/surface.d.ts.map +1 -0
  52. package/dist/tools/auth/surface.js +170 -0
  53. package/dist/tools/auth/user-providers.d.ts +15 -0
  54. package/dist/tools/auth/user-providers.d.ts.map +1 -0
  55. package/dist/tools/auth/user-providers.js +62 -0
  56. package/dist/tools/auth-block-gap.d.ts +6 -0
  57. package/dist/tools/auth-block-gap.d.ts.map +1 -1
  58. package/dist/tools/auth-block-gap.js +42 -9
  59. package/dist/tools/auth-detector.d.ts +9 -8
  60. package/dist/tools/auth-detector.d.ts.map +1 -1
  61. package/dist/tools/auth-detector.js +106 -8
  62. package/dist/tools/explorers/browser.d.ts +3 -0
  63. package/dist/tools/explorers/browser.d.ts.map +1 -0
  64. package/dist/tools/explorers/browser.js +13 -0
  65. package/dist/tools/explorers/cypress-explorer.d.ts +8 -0
  66. package/dist/tools/explorers/cypress-explorer.d.ts.map +1 -0
  67. package/dist/tools/explorers/cypress-explorer.js +5 -0
  68. package/dist/tools/explorers/cypress.d.ts +8 -0
  69. package/dist/tools/explorers/cypress.d.ts.map +1 -0
  70. package/dist/tools/explorers/cypress.js +5 -0
  71. package/dist/tools/explorers/explorer.interface.d.ts +7 -0
  72. package/dist/tools/explorers/explorer.interface.d.ts.map +1 -0
  73. package/dist/tools/explorers/explorer.interface.js +1 -0
  74. package/dist/tools/explorers/factory.d.ts +4 -0
  75. package/dist/tools/explorers/factory.d.ts.map +1 -0
  76. package/dist/tools/explorers/factory.js +12 -0
  77. package/dist/tools/explorers/playwright-explorer.d.ts +8 -0
  78. package/dist/tools/explorers/playwright-explorer.d.ts.map +1 -0
  79. package/dist/tools/explorers/playwright-explorer.js +172 -0
  80. package/dist/tools/explorers/playwright.d.ts +8 -0
  81. package/dist/tools/explorers/playwright.d.ts.map +1 -0
  82. package/dist/tools/explorers/playwright.js +172 -0
  83. package/dist/tools/explorers/types.d.ts +7 -0
  84. package/dist/tools/explorers/types.d.ts.map +1 -0
  85. package/dist/tools/explorers/types.js +1 -0
  86. package/dist/tools/playwright-explorer.js +1 -1
  87. package/dist/tools/repo/detect-framework.d.ts +15 -0
  88. package/dist/tools/repo/detect-framework.d.ts.map +1 -0
  89. package/dist/tools/repo/detect-framework.js +153 -0
  90. package/dist/tools/repo/framework-detector.d.ts +15 -0
  91. package/dist/tools/repo/framework-detector.d.ts.map +1 -0
  92. package/dist/tools/repo/framework-detector.js +153 -0
  93. package/dist/tools/repo/scan.d.ts +19 -0
  94. package/dist/tools/repo/scan.d.ts.map +1 -0
  95. package/dist/tools/repo/scan.js +181 -0
  96. package/dist/tools/repo/scanner.d.ts +19 -0
  97. package/dist/tools/repo/scanner.d.ts.map +1 -0
  98. package/dist/tools/repo/scanner.js +181 -0
  99. package/dist/tools/scoring/automation-maturity.d.ts +4 -0
  100. package/dist/tools/scoring/automation-maturity.d.ts.map +1 -0
  101. package/dist/tools/scoring/automation-maturity.js +219 -0
  102. package/dist/tools/scoring/gap-engine.d.ts +8 -0
  103. package/dist/tools/scoring/gap-engine.d.ts.map +1 -0
  104. package/dist/tools/scoring/gap-engine.js +138 -0
  105. package/dist/tools/scoring/gaps.d.ts +8 -0
  106. package/dist/tools/scoring/gaps.d.ts.map +1 -0
  107. package/dist/tools/scoring/gaps.js +138 -0
  108. package/dist/tools/scoring/public-surface.d.ts +5 -0
  109. package/dist/tools/scoring/public-surface.d.ts.map +1 -0
  110. package/dist/tools/scoring/public-surface.js +13 -0
  111. package/package.json +3 -3
package/README.md CHANGED
@@ -77,6 +77,24 @@ qulib analyze --url https://app.example.com --auth-storage-state ./qulib-storage
77
77
 
78
78
  The storage state is just a JSON file of cookies and localStorage — keep it private, treat it like a credential.
79
79
 
80
+ #### Storage state is validated before crawl
81
+
82
+ Qulib now validates the provided storage state before doing any work. If the file is missing, unreadable, empty, on the wrong origin, or carries a session that is already expired, Qulib stops with an honest `blocked` result (no fake `releaseConfidence`) and a structured gap explaining how to recover. The validator reports one of these stable reason codes:
83
+
84
+ | Reason code | Meaning |
85
+ | ------------------------- | ----------------------------------------------------------------------- |
86
+ | `missing-file` | Path passed to `--auth-storage-state` does not exist. |
87
+ | `unreadable-file` | File exists but the process can't read it (permissions). |
88
+ | `invalid-json` | File is present and readable but not valid JSON. |
89
+ | `no-auth-cookies` | File parses, but has zero cookies and zero localStorage entries. |
90
+ | `wrong-origin` | Session redirects to a different origin (host/port/scheme mismatch). |
91
+ | `expired-or-unauthorized` | Loaded session shows the login form again, or the app returns 401/403. |
92
+ | `unknown` | Validation could not be completed for an unexpected reason. |
93
+
94
+ Origin matching is strict — `https://app.example` and `https://www.app.example` are different origins, as are `http://localhost:3000` and `http://localhost:4000`. Re-run `qulib auth login` against the same origin you plan to `analyze`.
95
+
96
+ Relatedly, `qulib auth login` will now refuse to save a storage state if the browser ends the flow on a different origin than `--base-url` (a federated/SSO redirect that never returned to the app). This prevents Qulib from quietly persisting an IdP-domain session that would later produce false-confidence scans.
97
+
80
98
  ### Multi-path auth exploration (`explore-auth`)
81
99
 
82
100
  For unfamiliar apps (especially enterprise SSO with several buttons), run **`qulib explore-auth --url <url>`** before `analyze`. The JSON lists every detected path (built-in OAuth names like Google/Clever, **heuristic** unknown buttons such as tenant-specific SSO labels, password forms, and magic-link copy) plus **`suggestedAgentBehavior`** for the agent.
@@ -194,16 +212,24 @@ TypeScript (strict, NodeNext), Commander, Zod, Playwright, @axe-core/playwright,
194
212
  ```text
195
213
  src/
196
214
  adapters/ # test rendering adapters
197
- analyze.ts # programmatic API (also used by @qulib/mcp)
198
- cli/ # CLI entry
199
- harness/ # state + decision logging
200
- llm/ # LLM contracts
201
- phases/ # observe / think / act
202
- reporters/ # JSON + Markdown reports
203
- schemas/ # Zod schemas
204
- tools/ # explorers, auth, gap engine, repo scanner
215
+ analyze.ts # programmatic API (also used by @qulib/mcp)
216
+ cli/ # CLI entry
217
+ harness/ # state + decision logging
218
+ llm/ # LLM contracts
219
+ phases/ # observe / think / act
220
+ reporters/ # JSON + Markdown reports
221
+ schemas/ # Zod schemas
222
+ telemetry/ # event sink + URL redaction
223
+ tools/
224
+ auth/ # detection, exploration, validation, providers, gap builders
225
+ explorers/ # browser launch, Playwright/Cypress crawlers, factory
226
+ repo/ # repo scanner, framework detection
227
+ scoring/ # gap engine, automation maturity, public surface
228
+ __tests__/ # integration and wiring tests live in __tests__/ in each folder
205
229
  ```
206
230
 
231
+ A contributor map of which folder to touch for each kind of change lives at [`docs/source-map.md`](../../docs/source-map.md).
232
+
207
233
  Repo rules: see [`CLAUDE.md`](../../CLAUDE.md).
208
234
 
209
235
  ## Configuration
@@ -1 +1 @@
1
- {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../src/analyze.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAChG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAU7F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAGxE,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAE/D,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,aAAa,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,0GAA0G;IAC1G,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,sFAAsF;IACtF,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,WAAW,EAAE,WAAW,CAAC;IACzB,8GAA8G;IAC9G,cAAc,EAAE,cAAc,CAAC;IAC/B,aAAa,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC,WAAW,EAAE,gBAAgB,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,0HAA0H;IAC1H,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;CACrC;AAcD,wBAAsB,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CA8JhF"}
1
+ {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../src/analyze.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAChG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAU7F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAGxE,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAE/D,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,aAAa,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,0GAA0G;IAC1G,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,sFAAsF;IACtF,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,WAAW,EAAE,WAAW,CAAC;IACzB,8GAA8G;IAC9G,cAAc,EAAE,cAAc,CAAC;IAC/B,aAAa,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC,WAAW,EAAE,gBAAgB,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,0HAA0H;IAC1H,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;CACrC;AAcD,wBAAsB,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAqQhF"}
package/dist/analyze.js CHANGED
@@ -4,11 +4,11 @@ import { PublicSurfaceSchema } from './schemas/public-surface.schema.js';
4
4
  import { observe } from './phases/observe.js';
5
5
  import { think } from './phases/think.js';
6
6
  import { act } from './phases/act.js';
7
- import { detectAuth } from './tools/auth-detector.js';
8
- import { analyzeGaps, computeCoverageScore, computeQualityScoreFromGaps } from './tools/gap-engine.js';
9
- import { analyzeAuthSurfaceGaps } from './tools/auth-surface-analyzer.js';
10
- import { buildPublicSurface } from './tools/public-surface.js';
11
- import { buildAuthBlockGap } from './tools/auth-block-gap.js';
7
+ import { detectAuth, validateStorageState } from './tools/auth/detect.js';
8
+ import { analyzeGaps, computeCoverageScore, computeQualityScoreFromGaps } from './tools/scoring/gaps.js';
9
+ import { analyzeAuthSurfaceGaps } from './tools/auth/surface.js';
10
+ import { buildPublicSurface } from './tools/scoring/public-surface.js';
11
+ import { buildAuthBlockGap, buildStorageStateInvalidGap } from './tools/auth/gaps.js';
12
12
  import { finalizeGapAnalysisFromDraft } from './phases/think-finalize.js';
13
13
  import { emitTelemetry, redactUrlForTelemetry } from './telemetry/emit.js';
14
14
  function logScanEnd(progress, result) {
@@ -40,6 +40,85 @@ export async function analyzeApp(options) {
40
40
  hasAuth: Boolean(options.config.auth),
41
41
  });
42
42
  progress?.info(`Starting scan → ${options.url} maxPagesToScan=${options.config.maxPagesToScan}`);
43
+ if (options.config.auth?.type === 'storage-state') {
44
+ progress?.info('Validating provided storage state before crawl…');
45
+ const validation = await validateStorageState(options.url, options.config.auth.path, options.config.timeoutMs);
46
+ let targetOriginForTelemetry;
47
+ try {
48
+ targetOriginForTelemetry = new URL(options.url).origin;
49
+ }
50
+ catch {
51
+ targetOriginForTelemetry = '[unparseable-target-url]';
52
+ }
53
+ emitTelemetry(options.telemetry, 'auth.storage-state.validated', sessionId, {
54
+ targetOrigin: targetOriginForTelemetry,
55
+ valid: validation.valid,
56
+ reasonCode: validation.reasonCode,
57
+ storageStateProvided: true,
58
+ });
59
+ if (!validation.valid) {
60
+ progress?.warn(`Storage state rejected (${validation.reasonCode}): ${validation.reason}. Skipping crawl.`);
61
+ decisionLog.push({
62
+ timestamp: new Date().toISOString(),
63
+ phase: 'observe',
64
+ decision: 'storage-state-invalid',
65
+ reason: `${validation.reasonCode}: ${validation.reason}`,
66
+ metadata: {
67
+ reasonCode: validation.reasonCode,
68
+ targetOrigin: targetOriginForTelemetry,
69
+ },
70
+ });
71
+ const invalidGap = buildStorageStateInvalidGap({
72
+ url: options.url,
73
+ reasonCode: validation.reasonCode === 'ok' ? 'unknown' : validation.reasonCode,
74
+ reason: validation.reason,
75
+ });
76
+ const draft = {
77
+ analyzedAt: new Date().toISOString(),
78
+ mode: 'auth-required',
79
+ releaseConfidence: 0,
80
+ coveragePagesScanned: 0,
81
+ coverageBudgetExceeded: false,
82
+ coverageWarning: 'auth-required',
83
+ gaps: [invalidGap],
84
+ };
85
+ const costContext = {
86
+ mode: 'auth-required',
87
+ coveragePagesScanned: 0,
88
+ releaseConfidence: 0,
89
+ gaps: [invalidGap],
90
+ };
91
+ const gapAnalysis = await finalizeGapAnalysisFromDraft(draft, options.config, artifacts, costContext);
92
+ const emptyAuthRoutes = RouteInventorySchema.parse({
93
+ scannedAt: new Date().toISOString(),
94
+ baseUrl: options.url,
95
+ routes: [],
96
+ pagesSkipped: 0,
97
+ budgetExceeded: false,
98
+ });
99
+ await act(gapAnalysis, options.config, artifacts);
100
+ const blockedResult = {
101
+ status: 'blocked',
102
+ coverageScore: null,
103
+ releaseConfidence: 0,
104
+ gaps: gapAnalysis.gaps,
105
+ gapAnalysis,
106
+ routeInventory: emptyAuthRoutes,
107
+ repoInventory: null,
108
+ decisionLog,
109
+ publicSurface: null,
110
+ };
111
+ logScanEnd(progress, blockedResult);
112
+ emitTelemetry(options.telemetry, 'scan.blocked', sessionId, {
113
+ status: blockedResult.status,
114
+ coverageScore: blockedResult.coverageScore,
115
+ releaseConfidence: blockedResult.releaseConfidence,
116
+ gapCount: blockedResult.gaps.length,
117
+ reasonCode: validation.reasonCode,
118
+ });
119
+ return blockedResult;
120
+ }
121
+ }
43
122
  let detectedAuth;
44
123
  let authWall = false;
45
124
  if (!options.config.auth && !options.skipAuthDetection) {
@@ -1 +1 @@
1
- {"version":3,"file":"auth-login-run.d.ts","sourceRoot":"","sources":["../../src/cli/auth-login-run.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAqB5D,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAEhE;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4GhB"}
1
+ {"version":3,"file":"auth-login-run.d.ts","sourceRoot":"","sources":["../../src/cli/auth-login-run.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAsB5D,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAEhE;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoIhB"}
@@ -1,4 +1,5 @@
1
- import { BUILT_IN_OAUTH_PROVIDERS } from '../tools/oauth-providers.js';
1
+ import { BUILT_IN_OAUTH_PROVIDERS } from '../tools/auth/providers.js';
2
+ import { waitForReturnToOrigin } from '../tools/auth/detect.js';
2
3
  const builtInOAuthIds = new Set(BUILT_IN_OAUTH_PROVIDERS.map((p) => p.id));
3
4
  function escapeRegExp(s) {
4
5
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -109,8 +110,31 @@ export async function runAutomatedAuthLogin(params) {
109
110
  await sleep(250);
110
111
  }
111
112
  }
113
+ const originReturn = await waitForReturnToOrigin(page, params.baseUrlHint, params.timeoutMs);
114
+ if (!originReturn.returned) {
115
+ let targetOrigin = '<unknown>';
116
+ let finalOrigin = '<unknown>';
117
+ try {
118
+ targetOrigin = new URL(params.baseUrlHint).origin;
119
+ }
120
+ catch {
121
+ /* targetOrigin stays <unknown> */
122
+ }
123
+ try {
124
+ finalOrigin = new URL(originReturn.finalUrl).origin;
125
+ }
126
+ catch {
127
+ /* finalOrigin stays <unknown> */
128
+ }
129
+ throw new Error(`Login flow did not return to the app origin (expected ${targetOrigin}, final ${finalOrigin}). ` +
130
+ `Refusing to save the storage state — it would belong to the wrong domain and produce ` +
131
+ `false-confidence scans. Retry the login (the federated provider may need a redirect tweak) ` +
132
+ `or capture the session manually with \`qulib auth init --base-url ${params.baseUrlHint}\`.`);
133
+ }
112
134
  if (!confirmed) {
113
- console.error('[qulib] Could not confirm login success. Storage state saved; verify manually before relying on it.');
135
+ console.error('[qulib] Could not confirm login success heuristically, but the browser ended on the app origin. ' +
136
+ 'Storage state will be saved; verify the session before relying on it (run `qulib analyze` ' +
137
+ 'and check that releaseConfidence is not null).');
114
138
  }
115
139
  const fs = await import('node:fs/promises');
116
140
  const pathMod = await import('node:path');
package/dist/cli/index.js CHANGED
@@ -8,8 +8,8 @@ const requirePkg = createRequire(import.meta.url);
8
8
  const pkg = requirePkg('../../package.json');
9
9
  import { HarnessConfigSchema } from '../schemas/config.schema.js';
10
10
  import { analyzeApp } from '../analyze.js';
11
- import { detectAuth } from '../tools/auth-detector.js';
12
- import { exploreAuth } from '../tools/auth-explorer.js';
11
+ import { detectAuth } from '../tools/auth/detect.js';
12
+ import { exploreAuth } from '../tools/auth/explore.js';
13
13
  import { assertExactlyOneCredentialSource, parseCredentialsJsonString, resolveAuthLoginConfig, } from './auth-login-resolve.js';
14
14
  import { runAutomatedAuthLogin } from './auth-login-run.js';
15
15
  const program = new Command();
@@ -33,11 +33,14 @@ function redactConfigForLog(config) {
33
33
  base.auth = {
34
34
  ...config.auth,
35
35
  credentials: {
36
- username: config.auth.credentials.username,
36
+ username: '***',
37
37
  password: '***',
38
38
  },
39
39
  };
40
40
  }
41
+ if (config.auth?.type === 'storage-state') {
42
+ base.auth = { type: 'storage-state', path: '<provided>' };
43
+ }
41
44
  return base;
42
45
  }
43
46
  function mergeAuthFromCli(config, options) {
@@ -212,7 +215,7 @@ providersCmd
212
215
  .command('list')
213
216
  .description('List user-local providers registered on this machine')
214
217
  .action(async () => {
215
- const { listUserProviders } = await import('../tools/user-providers.js');
218
+ const { listUserProviders } = await import('../tools/auth/custom-providers.js');
216
219
  const providers = listUserProviders();
217
220
  console.log(JSON.stringify(providers, null, 2));
218
221
  });
@@ -229,7 +232,7 @@ providersCmd
229
232
  catch {
230
233
  throw new Error(`Invalid regex pattern: ${opts.pattern}`);
231
234
  }
232
- const { addUserProvider } = await import('../tools/user-providers.js');
235
+ const { addUserProvider } = await import('../tools/auth/custom-providers.js');
233
236
  addUserProvider({ id: opts.id, label: opts.label, pattern: opts.pattern });
234
237
  console.log(`[qulib] Added provider "${opts.label}" (id: ${opts.id}) to ~/.qulib/providers.json`);
235
238
  });
@@ -238,7 +241,7 @@ providersCmd
238
241
  .description('Remove a user-local provider by id')
239
242
  .requiredOption('--id <id>', 'Provider id to remove')
240
243
  .action(async (opts) => {
241
- const { removeUserProvider } = await import('../tools/user-providers.js');
244
+ const { removeUserProvider } = await import('../tools/auth/custom-providers.js');
242
245
  const removed = removeUserProvider(opts.id);
243
246
  console.log(removed ? `[qulib] Removed "${opts.id}"` : `[qulib] No provider with id "${opts.id}" found`);
244
247
  });
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  export { analyzeApp } from './analyze.js';
2
- export { detectAuth } from './tools/auth-detector.js';
3
- export { exploreAuth } from './tools/auth-explorer.js';
4
- export { addUserProvider, removeUserProvider, listUserProviders } from './tools/user-providers.js';
5
- export { scanRepo } from './tools/repo-scanner.js';
6
- export { computeAutomationMaturity } from './tools/automation-maturity.js';
2
+ export { detectAuth, validateStorageState, evaluateStorageStateValidity, preflightStorageStateFile, waitForReturnToOrigin, } from './tools/auth/detect.js';
3
+ export type { StorageStateInvalidReason, StorageStateValidationResult, } from './tools/auth/detect.js';
4
+ export { exploreAuth } from './tools/auth/explore.js';
5
+ export { addUserProvider, removeUserProvider, listUserProviders } from './tools/auth/custom-providers.js';
6
+ export { scanRepo } from './tools/repo/scan.js';
7
+ export { computeAutomationMaturity } from './tools/scoring/automation-maturity.js';
7
8
  export { createProvider } from './llm/provider-registry.js';
8
9
  export { resolveMaxOutputTokensPerLlmCall } from './schemas/config.schema.js';
9
10
  export { resolveScanStateBaseDir, resolveReportDir } from './harness/state-manager.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnG,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,gCAAgC,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACvF,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACjF,YAAY,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,YAAY,EACV,aAAa,EACb,cAAc,EACd,kBAAkB,GACnB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,YAAY,EACV,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,aAAa,EACb,kBAAkB,EAClB,2BAA2B,EAC3B,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,4BAA4B,EAC5B,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC1G,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,gCAAgC,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACvF,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACjF,YAAY,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,YAAY,EACV,aAAa,EACb,cAAc,EACd,kBAAkB,GACnB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC9E,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,YAAY,EACV,aAAa,EACb,UAAU,EACV,cAAc,EACd,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,qBAAqB,EACrB,aAAa,EACb,kBAAkB,EAClB,2BAA2B,EAC3B,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  export { analyzeApp } from './analyze.js';
2
- export { detectAuth } from './tools/auth-detector.js';
3
- export { exploreAuth } from './tools/auth-explorer.js';
4
- export { addUserProvider, removeUserProvider, listUserProviders } from './tools/user-providers.js';
5
- export { scanRepo } from './tools/repo-scanner.js';
6
- export { computeAutomationMaturity } from './tools/automation-maturity.js';
2
+ export { detectAuth, validateStorageState, evaluateStorageStateValidity, preflightStorageStateFile, waitForReturnToOrigin, } from './tools/auth/detect.js';
3
+ export { exploreAuth } from './tools/auth/explore.js';
4
+ export { addUserProvider, removeUserProvider, listUserProviders } from './tools/auth/custom-providers.js';
5
+ export { scanRepo } from './tools/repo/scan.js';
6
+ export { computeAutomationMaturity } from './tools/scoring/automation-maturity.js';
7
7
  export { createProvider } from './llm/provider-registry.js';
8
8
  export { resolveMaxOutputTokensPerLlmCall } from './schemas/config.schema.js';
9
9
  export { resolveScanStateBaseDir, resolveReportDir } from './harness/state-manager.js';
@@ -1,7 +1,7 @@
1
1
  import { RouteInventorySchema } from '../schemas/route-inventory.schema.js';
2
2
  import { RepoAnalysisSchema } from '../schemas/repo-analysis.schema.js';
3
- import { createExplorer } from '../tools/explorer-factory.js';
4
- import { scanRepo } from '../tools/repo-scanner.js';
3
+ import { createExplorer } from '../tools/explorers/factory.js';
4
+ import { scanRepo } from '../tools/repo/scan.js';
5
5
  import { StateManager } from '../harness/state-manager.js';
6
6
  import { logDecision } from '../harness/decision-logger.js';
7
7
  import { emitTelemetry, redactUrlForTelemetry } from '../telemetry/emit.js';
@@ -1,5 +1,5 @@
1
1
  import { GapAnalysisSchema } from '../schemas/gap-analysis.schema.js';
2
- import { analyzeGaps } from '../tools/gap-engine.js';
2
+ import { analyzeGaps } from '../tools/scoring/gaps.js';
3
3
  import { logDecision } from '../harness/decision-logger.js';
4
4
  import { finalizeGapAnalysisFromDraft } from './think-finalize.js';
5
5
  import { emitTelemetry } from '../telemetry/emit.js';
@@ -1,4 +1,4 @@
1
- export type TelemetryEventKind = 'scan.started' | 'scan.completed' | 'scan.blocked' | 'phase.observe.started' | 'phase.observe.completed' | 'phase.think.started' | 'phase.think.completed' | 'phase.act.started' | 'phase.act.completed' | 'llm.call.started' | 'llm.call.completed' | 'llm.call.failed' | 'gap.detected' | 'auth.detected' | 'repo.scanned';
1
+ export type TelemetryEventKind = 'scan.started' | 'scan.completed' | 'scan.blocked' | 'phase.observe.started' | 'phase.observe.completed' | 'phase.think.started' | 'phase.think.completed' | 'phase.act.started' | 'phase.act.completed' | 'llm.call.started' | 'llm.call.completed' | 'llm.call.failed' | 'gap.detected' | 'auth.detected' | 'auth.storage-state.validated' | 'repo.scanned';
2
2
  export interface TelemetryEvent {
3
3
  kind: TelemetryEventKind;
4
4
  timestamp: string;
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry.interface.d.ts","sourceRoot":"","sources":["../../src/telemetry/telemetry.interface.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAC1B,cAAc,GACd,gBAAgB,GAChB,cAAc,GACd,uBAAuB,GACvB,yBAAyB,GACzB,qBAAqB,GACrB,uBAAuB,GACvB,mBAAmB,GACnB,qBAAqB,GACrB,kBAAkB,GAClB,oBAAoB,GACpB,iBAAiB,GACjB,cAAc,GACd,eAAe,GACf,cAAc,CAAC;AAEnB,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,kBAAkB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;CACnC;AAED,eAAO,MAAM,iBAAiB,EAAE,aAE/B,CAAC"}
1
+ {"version":3,"file":"telemetry.interface.d.ts","sourceRoot":"","sources":["../../src/telemetry/telemetry.interface.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAC1B,cAAc,GACd,gBAAgB,GAChB,cAAc,GACd,uBAAuB,GACvB,yBAAyB,GACzB,qBAAqB,GACrB,uBAAuB,GACvB,mBAAmB,GACnB,qBAAqB,GACrB,kBAAkB,GAClB,oBAAoB,GACpB,iBAAiB,GACjB,cAAc,GACd,eAAe,GACf,8BAA8B,GAC9B,cAAc,CAAC;AAEnB,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,kBAAkB,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;CACnC;AAED,eAAO,MAAM,iBAAiB,EAAE,aAE/B,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Browser, BrowserContext } from '@playwright/test';
2
+ import type { AuthConfig } from '../schemas/config.schema.js';
3
+ export declare function createAuthenticatedContext(browser: Browser, auth: AuthConfig | undefined, timeoutMs: number): Promise<BrowserContext>;
4
+ //# sourceMappingURL=apply-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-auth.d.ts","sourceRoot":"","sources":["../../src/tools/apply-auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEhE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,UAAU,GAAG,SAAS,EAC5B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC,CAsCzB"}
@@ -0,0 +1,35 @@
1
+ import { resolve } from 'node:path';
2
+ export async function createAuthenticatedContext(browser, auth, timeoutMs) {
3
+ if (!auth) {
4
+ return browser.newContext();
5
+ }
6
+ if (auth.type === 'storage-state') {
7
+ const storagePath = resolve(process.cwd(), auth.path);
8
+ return browser.newContext({ storageState: storagePath });
9
+ }
10
+ const context = await browser.newContext();
11
+ const page = await context.newPage();
12
+ try {
13
+ await page.goto(auth.loginUrl, { timeout: timeoutMs, waitUntil: 'domcontentloaded' });
14
+ await page.fill(auth.selectors.username, auth.credentials.username);
15
+ await page.fill(auth.selectors.password, auth.credentials.password);
16
+ await page.click(auth.selectors.submit);
17
+ const urlFragment = auth.successIndicator.urlContains;
18
+ if (urlFragment) {
19
+ await page.waitForURL((url) => url.toString().includes(urlFragment), {
20
+ timeout: timeoutMs,
21
+ });
22
+ }
23
+ const visibleSelector = auth.successIndicator.selectorVisible;
24
+ if (visibleSelector) {
25
+ await page.waitForSelector(visibleSelector, {
26
+ timeout: timeoutMs,
27
+ state: 'visible',
28
+ });
29
+ }
30
+ }
31
+ finally {
32
+ await page.close();
33
+ }
34
+ return context;
35
+ }
@@ -0,0 +1,4 @@
1
+ import type { Browser, BrowserContext } from '@playwright/test';
2
+ import type { AuthConfig } from '../../schemas/config.schema.js';
3
+ export declare function createAuthenticatedContext(browser: Browser, auth: AuthConfig | undefined, timeoutMs: number): Promise<BrowserContext>;
4
+ //# sourceMappingURL=apply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../../src/tools/auth/apply.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEhE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAEjE,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,UAAU,GAAG,SAAS,EAC5B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC,CAsCzB"}
@@ -0,0 +1,35 @@
1
+ import { resolve } from 'node:path';
2
+ export async function createAuthenticatedContext(browser, auth, timeoutMs) {
3
+ if (!auth) {
4
+ return browser.newContext();
5
+ }
6
+ if (auth.type === 'storage-state') {
7
+ const storagePath = resolve(process.cwd(), auth.path);
8
+ return browser.newContext({ storageState: storagePath });
9
+ }
10
+ const context = await browser.newContext();
11
+ const page = await context.newPage();
12
+ try {
13
+ await page.goto(auth.loginUrl, { timeout: timeoutMs, waitUntil: 'domcontentloaded' });
14
+ await page.fill(auth.selectors.username, auth.credentials.username);
15
+ await page.fill(auth.selectors.password, auth.credentials.password);
16
+ await page.click(auth.selectors.submit);
17
+ const urlFragment = auth.successIndicator.urlContains;
18
+ if (urlFragment) {
19
+ await page.waitForURL((url) => url.toString().includes(urlFragment), {
20
+ timeout: timeoutMs,
21
+ });
22
+ }
23
+ const visibleSelector = auth.successIndicator.selectorVisible;
24
+ if (visibleSelector) {
25
+ await page.waitForSelector(visibleSelector, {
26
+ timeout: timeoutMs,
27
+ state: 'visible',
28
+ });
29
+ }
30
+ }
31
+ finally {
32
+ await page.close();
33
+ }
34
+ return context;
35
+ }
@@ -0,0 +1,9 @@
1
+ import type { Gap } from '../../schemas/gap-analysis.schema.js';
2
+ import type { StorageStateInvalidReason } from './detector.js';
3
+ export declare function buildAuthBlockGap(url: string): Gap;
4
+ export declare function buildStorageStateInvalidGap(input: {
5
+ url: string;
6
+ reasonCode: StorageStateInvalidReason;
7
+ reason: string;
8
+ }): Gap;
9
+ //# sourceMappingURL=block-gap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block-gap.d.ts","sourceRoot":"","sources":["../../../src/tools/auth/block-gap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,sCAAsC,CAAC;AAChE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAmB/D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAYlD;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,yBAAyB,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,GAAG,CAqBN"}
@@ -0,0 +1,52 @@
1
+ function safeOriginAndPath(url) {
2
+ try {
3
+ const u = new URL(url);
4
+ return `${u.origin}${u.pathname}`;
5
+ }
6
+ catch {
7
+ return url;
8
+ }
9
+ }
10
+ function safeHost(url) {
11
+ try {
12
+ return new URL(url).hostname;
13
+ }
14
+ catch {
15
+ return url;
16
+ }
17
+ }
18
+ export function buildAuthBlockGap(url) {
19
+ const host = safeHost(url);
20
+ const safeUrl = safeOriginAndPath(url);
21
+ return {
22
+ id: 'auth-block',
23
+ path: '/',
24
+ severity: 'critical',
25
+ category: 'coverage',
26
+ reason: `Scan blocked by authentication. No authenticated pages were evaluated for ${host}.`,
27
+ description: 'Scan blocked by authentication. 0 authenticated pages were evaluated.',
28
+ recommendation: `Run \`qulib auth init --base-url ${safeUrl}\` to capture a storage state, then re-run with --auth storage-state.`,
29
+ };
30
+ }
31
+ export function buildStorageStateInvalidGap(input) {
32
+ const host = safeHost(input.url);
33
+ const safeUrl = safeOriginAndPath(input.url);
34
+ const recoveryByCode = {
35
+ 'missing-file': `Storage state file was not found. Run \`qulib auth login --base-url ${safeUrl} --out <path>\` (or \`qulib auth init\`) to capture a fresh state, then re-run \`qulib analyze --url ${safeUrl} --auth-storage-state <path>\`.`,
36
+ 'unreadable-file': `Storage state file exists but could not be read. Check file permissions, then re-run \`qulib auth login\` if needed.`,
37
+ 'invalid-json': `Storage state file is not valid JSON. Run \`qulib auth login --base-url ${safeUrl} --out <path>\` again to regenerate it.`,
38
+ 'wrong-origin': `Storage state belongs to a different origin than ${host}. Re-run \`qulib auth login --base-url ${safeUrl}\` against this target and pass the new file to \`qulib analyze\`.`,
39
+ 'expired-or-unauthorized': `The session in the storage state has expired or is unauthorized. Run \`qulib auth login --base-url ${safeUrl}\` to capture a fresh state, then re-run \`qulib analyze --url ${safeUrl} --auth-storage-state <path>\`.`,
40
+ 'no-auth-cookies': `Storage state file contains no cookies or localStorage entries — it is effectively empty. Run \`qulib auth login --base-url ${safeUrl}\` to capture a real session.`,
41
+ unknown: `Storage state could not be validated. Try \`qulib auth login --base-url ${safeUrl}\` again, and verify the file was saved on the same origin.`,
42
+ };
43
+ return {
44
+ id: 'storage-state-invalid',
45
+ path: '/',
46
+ severity: 'critical',
47
+ category: 'coverage',
48
+ reason: `Authenticated scan could not continue because the provided storage state is invalid for ${host}. Reason: ${input.reasonCode} — ${input.reason}.`,
49
+ description: `Storage state validation failed before crawling. The session was checked against ${host} and rejected with reason code "${input.reasonCode}".`,
50
+ recommendation: recoveryByCode[input.reasonCode],
51
+ };
52
+ }
@@ -0,0 +1,15 @@
1
+ import type { OAuthProvider } from './providers.js';
2
+ export interface SerializedProvider {
3
+ id: string;
4
+ label: string;
5
+ patterns: string[];
6
+ }
7
+ export declare function loadUserProviders(): OAuthProvider[];
8
+ export declare function addUserProvider(input: {
9
+ id: string;
10
+ label: string;
11
+ pattern: string;
12
+ }): void;
13
+ export declare function removeUserProvider(id: string): boolean;
14
+ export declare function listUserProviders(): SerializedProvider[];
15
+ //# sourceMappingURL=custom-providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-providers.d.ts","sourceRoot":"","sources":["../../../src/tools/auth/custom-providers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAIpD,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,iBAAiB,IAAI,aAAa,EAAE,CAOnD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAc3F;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAStD;AAED,wBAAgB,iBAAiB,IAAI,kBAAkB,EAAE,CAExD"}
@@ -0,0 +1,62 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ const USER_PROVIDERS_PATH = join(homedir(), '.qulib', 'providers.json');
5
+ export function loadUserProviders() {
6
+ const raw = loadSerialized();
7
+ return raw.map((p) => ({
8
+ id: p.id,
9
+ label: p.label,
10
+ patterns: p.patterns.map((src) => new RegExp(src, 'i')),
11
+ }));
12
+ }
13
+ export function addUserProvider(input) {
14
+ const existing = loadSerialized();
15
+ const idx = existing.findIndex((p) => p.id === input.id);
16
+ if (idx >= 0) {
17
+ const p = existing[idx];
18
+ if (!p.patterns.includes(input.pattern)) {
19
+ p.patterns.push(input.pattern);
20
+ }
21
+ p.label = input.label;
22
+ }
23
+ else {
24
+ existing.push({ id: input.id, label: input.label, patterns: [input.pattern] });
25
+ }
26
+ ensureDir();
27
+ writeFileSync(USER_PROVIDERS_PATH, JSON.stringify(existing, null, 2), 'utf-8');
28
+ }
29
+ export function removeUserProvider(id) {
30
+ const existing = loadSerialized();
31
+ const filtered = existing.filter((p) => p.id !== id);
32
+ if (filtered.length === existing.length) {
33
+ return false;
34
+ }
35
+ ensureDir();
36
+ writeFileSync(USER_PROVIDERS_PATH, JSON.stringify(filtered, null, 2), 'utf-8');
37
+ return true;
38
+ }
39
+ export function listUserProviders() {
40
+ return loadSerialized();
41
+ }
42
+ function loadSerialized() {
43
+ if (!existsSync(USER_PROVIDERS_PATH)) {
44
+ return [];
45
+ }
46
+ try {
47
+ const parsed = JSON.parse(readFileSync(USER_PROVIDERS_PATH, 'utf-8'));
48
+ if (!Array.isArray(parsed)) {
49
+ return [];
50
+ }
51
+ return parsed;
52
+ }
53
+ catch {
54
+ return [];
55
+ }
56
+ }
57
+ function ensureDir() {
58
+ const dir = dirname(USER_PROVIDERS_PATH);
59
+ if (!existsSync(dir)) {
60
+ mkdirSync(dir, { recursive: true });
61
+ }
62
+ }
@@ -0,0 +1,23 @@
1
+ import type { Page } from '@playwright/test';
2
+ import type { DetectedAuth } from '../../schemas/config.schema.js';
3
+ import type { AnalyzeProgressSink } from '../../harness/progress-log.js';
4
+ export type StorageStateInvalidReason = 'missing-file' | 'unreadable-file' | 'invalid-json' | 'wrong-origin' | 'expired-or-unauthorized' | 'no-auth-cookies' | 'unknown';
5
+ export interface StorageStateValidationResult {
6
+ valid: boolean;
7
+ reasonCode: StorageStateInvalidReason | 'ok';
8
+ reason: string;
9
+ }
10
+ export declare function evaluateStorageStateValidity(signals: {
11
+ expectedOrigin: string;
12
+ finalUrl: string;
13
+ visiblePasswordCount: number;
14
+ hadUnauthorizedHttp: boolean;
15
+ }): StorageStateValidationResult;
16
+ export declare function preflightStorageStateFile(storagePath: string): Promise<StorageStateValidationResult | null>;
17
+ export declare function waitForReturnToOrigin(page: Page, baseUrl: string, timeoutMs?: number): Promise<{
18
+ returned: boolean;
19
+ finalUrl: string;
20
+ }>;
21
+ export declare function validateStorageState(url: string, storagePath: string, timeoutMs?: number): Promise<StorageStateValidationResult>;
22
+ export declare function detectAuth(url: string, timeoutMs?: number, progress?: AnalyzeProgressSink): Promise<DetectedAuth>;
23
+ //# sourceMappingURL=detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../../src/tools/auth/detect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAY,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAIzE,MAAM,MAAM,yBAAyB,GACjC,cAAc,GACd,iBAAiB,GACjB,cAAc,GACd,cAAc,GACd,yBAAyB,GACzB,iBAAiB,GACjB,SAAS,CAAC;AAEd,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC;CAChB;AAsQD,wBAAgB,4BAA4B,CAAC,OAAO,EAAE;IACpD,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,EAAE,OAAO,CAAC;CAC9B,GAAG,4BAA4B,CA6B/B;AAOD,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,4BAA4B,GAAG,IAAI,CAAC,CAgD9C;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,EACf,SAAS,SAAQ,GAChB,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAelD;AAED,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,EACnB,SAAS,SAAQ,GAChB,OAAO,CAAC,4BAA4B,CAAC,CAyDvC;AAED,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,SAAS,SAAQ,EACjB,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAqJvB"}