@avi770/testteam 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -0
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/agents/01-analyst.ts +100 -0
- package/agents/02-seed-architect.ts +59 -0
- package/agents/03-test-generator.ts +191 -0
- package/agents/04-unit-runner.ts +160 -0
- package/agents/05-browser-crawler.ts +790 -0
- package/agents/06-api-exerciser.ts +311 -0
- package/agents/07-security-scout.ts +188 -0
- package/agents/08-a11y-guardian.ts +212 -0
- package/agents/09-healer.ts +228 -0
- package/agents/10-reporter.ts +266 -0
- package/agents/11-fixer.ts +253 -0
- package/agents/12-ux-inspector.ts +444 -0
- package/agents/13-performance-profiler.ts +271 -0
- package/agents/14-data-integrity-auditor.ts +417 -0
- package/agents/15-regression-sentinel.ts +307 -0
- package/agents/16-chaos-agent.ts +228 -0
- package/agents/17-documentation-validator.ts +266 -0
- package/agents/18-integration-watchdog.ts +178 -0
- package/agents/19-tenant-isolation-auditor.ts +199 -0
- package/agents/20-workflow-completion-tester.ts +203 -0
- package/agents/21-state-session-tester.ts +262 -0
- package/agents/22-email-notification-verifier.ts +244 -0
- package/agents/23-migration-tester.ts +80 -0
- package/agents/__tests__/01-analyst.test.ts +188 -0
- package/agents/__tests__/02-seed-architect.test.ts +152 -0
- package/agents/__tests__/03-test-generator-full.test.ts +321 -0
- package/agents/__tests__/03-test-generator.test.ts +318 -0
- package/agents/__tests__/04-unit-runner.test.ts +320 -0
- package/agents/__tests__/05-browser-crawler-beta.test.ts +492 -0
- package/agents/__tests__/05-browser-crawler-release.test.ts +412 -0
- package/agents/__tests__/05-browser-crawler-uat.test.ts +578 -0
- package/agents/__tests__/05-browser-crawler.test.ts +518 -0
- package/agents/__tests__/06-api-exerciser.test.ts +619 -0
- package/agents/__tests__/07-security-scout.test.ts +382 -0
- package/agents/__tests__/08-a11y-guardian.test.ts +530 -0
- package/agents/__tests__/09-healer.test.ts +384 -0
- package/agents/__tests__/10-reporter.test.ts +366 -0
- package/agents/__tests__/11-fixer.test.ts +406 -0
- package/agents/__tests__/12-ux-inspector-extended.test.ts +465 -0
- package/agents/__tests__/12-ux-inspector.test.ts +443 -0
- package/agents/__tests__/13-performance-profiler.test.ts +411 -0
- package/agents/__tests__/14-data-integrity-auditor-extended.test.ts +573 -0
- package/agents/__tests__/14-data-integrity-auditor.test.ts +407 -0
- package/agents/__tests__/15-regression-sentinel.test.ts +657 -0
- package/agents/__tests__/16-chaos-agent.test.ts +427 -0
- package/agents/__tests__/17-documentation-validator.test.ts +402 -0
- package/agents/__tests__/18-integration-watchdog.test.ts +263 -0
- package/agents/__tests__/19-tenant-isolation-auditor.test.ts +400 -0
- package/agents/__tests__/20-workflow-completion-tester.test.ts +586 -0
- package/agents/__tests__/21-state-session-tester.test.ts +374 -0
- package/agents/__tests__/22-email-notification-verifier.test.ts +441 -0
- package/agents/__tests__/23-migration-tester.test.ts +145 -0
- package/agents/__tests__/base-agent.test.ts +188 -0
- package/agents/__tests__/registry.test.ts +218 -0
- package/agents/base-agent.ts +77 -0
- package/agents/registry.ts +136 -0
- package/baselines/api-schemas/.gitkeep +0 -0
- package/baselines/performance/.gitkeep +0 -0
- package/baselines/screenshots/.gitkeep +0 -0
- package/bin/testteam.js +10 -0
- package/core/__tests__/ci-output.test.ts +430 -0
- package/core/__tests__/cli.test.ts +387 -0
- package/core/__tests__/config.test.ts +78 -0
- package/core/__tests__/cost-tracker.test.ts +158 -0
- package/core/__tests__/evidence.test.ts +265 -0
- package/core/__tests__/fix-loop.test.ts +210 -0
- package/core/__tests__/health-check.test.ts +44 -0
- package/core/__tests__/init.test.ts +609 -0
- package/core/__tests__/integration.test.ts +204 -0
- package/core/__tests__/license-gen.test.ts +227 -0
- package/core/__tests__/license.test.ts +326 -0
- package/core/__tests__/multi-browser.test.ts +278 -0
- package/core/__tests__/orchestrator.test.ts +519 -0
- package/core/__tests__/phase-gate.test.ts +43 -0
- package/core/__tests__/report-html.test.ts +398 -0
- package/core/__tests__/report-upload.test.ts +325 -0
- package/core/__tests__/run-counter.test.ts +234 -0
- package/core/ci-output.ts +240 -0
- package/core/cli.ts +232 -0
- package/core/config.ts +178 -0
- package/core/cost-tracker.ts +59 -0
- package/core/evidence.ts +132 -0
- package/core/fix-loop.ts +85 -0
- package/core/health-check.ts +54 -0
- package/core/init.ts +546 -0
- package/core/license-gen.ts +212 -0
- package/core/license.ts +211 -0
- package/core/messages.ts +67 -0
- package/core/multi-browser.ts +136 -0
- package/core/orchestrator.ts +354 -0
- package/core/phase-gate.ts +55 -0
- package/core/report-html.ts +657 -0
- package/core/report-upload.ts +188 -0
- package/core/run-counter.ts +175 -0
- package/core/types.ts +57 -0
- package/dist/agents/01-analyst.d.ts +11 -0
- package/dist/agents/01-analyst.d.ts.map +1 -0
- package/dist/agents/01-analyst.js +75 -0
- package/dist/agents/01-analyst.js.map +1 -0
- package/dist/agents/02-seed-architect.d.ts +11 -0
- package/dist/agents/02-seed-architect.d.ts.map +1 -0
- package/dist/agents/02-seed-architect.js +51 -0
- package/dist/agents/02-seed-architect.js.map +1 -0
- package/dist/agents/03-test-generator.d.ts +9 -0
- package/dist/agents/03-test-generator.d.ts.map +1 -0
- package/dist/agents/03-test-generator.js +167 -0
- package/dist/agents/03-test-generator.js.map +1 -0
- package/dist/agents/04-unit-runner.d.ts +9 -0
- package/dist/agents/04-unit-runner.d.ts.map +1 -0
- package/dist/agents/04-unit-runner.js +113 -0
- package/dist/agents/04-unit-runner.js.map +1 -0
- package/dist/agents/05-browser-crawler.d.ts +30 -0
- package/dist/agents/05-browser-crawler.d.ts.map +1 -0
- package/dist/agents/05-browser-crawler.js +685 -0
- package/dist/agents/05-browser-crawler.js.map +1 -0
- package/dist/agents/06-api-exerciser.d.ts +23 -0
- package/dist/agents/06-api-exerciser.d.ts.map +1 -0
- package/dist/agents/06-api-exerciser.js +253 -0
- package/dist/agents/06-api-exerciser.js.map +1 -0
- package/dist/agents/07-security-scout.d.ts +11 -0
- package/dist/agents/07-security-scout.d.ts.map +1 -0
- package/dist/agents/07-security-scout.js +142 -0
- package/dist/agents/07-security-scout.js.map +1 -0
- package/dist/agents/08-a11y-guardian.d.ts +13 -0
- package/dist/agents/08-a11y-guardian.d.ts.map +1 -0
- package/dist/agents/08-a11y-guardian.js +176 -0
- package/dist/agents/08-a11y-guardian.js.map +1 -0
- package/dist/agents/09-healer.d.ts +33 -0
- package/dist/agents/09-healer.d.ts.map +1 -0
- package/dist/agents/09-healer.js +167 -0
- package/dist/agents/09-healer.js.map +1 -0
- package/dist/agents/10-reporter.d.ts +26 -0
- package/dist/agents/10-reporter.d.ts.map +1 -0
- package/dist/agents/10-reporter.js +215 -0
- package/dist/agents/10-reporter.js.map +1 -0
- package/dist/agents/11-fixer.d.ts +26 -0
- package/dist/agents/11-fixer.d.ts.map +1 -0
- package/dist/agents/11-fixer.js +195 -0
- package/dist/agents/11-fixer.js.map +1 -0
- package/dist/agents/12-ux-inspector.d.ts +15 -0
- package/dist/agents/12-ux-inspector.d.ts.map +1 -0
- package/dist/agents/12-ux-inspector.js +364 -0
- package/dist/agents/12-ux-inspector.js.map +1 -0
- package/dist/agents/13-performance-profiler.d.ts +13 -0
- package/dist/agents/13-performance-profiler.d.ts.map +1 -0
- package/dist/agents/13-performance-profiler.js +216 -0
- package/dist/agents/13-performance-profiler.js.map +1 -0
- package/dist/agents/14-data-integrity-auditor.d.ts +12 -0
- package/dist/agents/14-data-integrity-auditor.d.ts.map +1 -0
- package/dist/agents/14-data-integrity-auditor.js +356 -0
- package/dist/agents/14-data-integrity-auditor.js.map +1 -0
- package/dist/agents/15-regression-sentinel.d.ts +25 -0
- package/dist/agents/15-regression-sentinel.d.ts.map +1 -0
- package/dist/agents/15-regression-sentinel.js +251 -0
- package/dist/agents/15-regression-sentinel.js.map +1 -0
- package/dist/agents/16-chaos-agent.d.ts +9 -0
- package/dist/agents/16-chaos-agent.d.ts.map +1 -0
- package/dist/agents/16-chaos-agent.js +207 -0
- package/dist/agents/16-chaos-agent.js.map +1 -0
- package/dist/agents/17-documentation-validator.d.ts +31 -0
- package/dist/agents/17-documentation-validator.d.ts.map +1 -0
- package/dist/agents/17-documentation-validator.js +246 -0
- package/dist/agents/17-documentation-validator.js.map +1 -0
- package/dist/agents/18-integration-watchdog.d.ts +10 -0
- package/dist/agents/18-integration-watchdog.d.ts.map +1 -0
- package/dist/agents/18-integration-watchdog.js +138 -0
- package/dist/agents/18-integration-watchdog.js.map +1 -0
- package/dist/agents/19-tenant-isolation-auditor.d.ts +9 -0
- package/dist/agents/19-tenant-isolation-auditor.d.ts.map +1 -0
- package/dist/agents/19-tenant-isolation-auditor.js +166 -0
- package/dist/agents/19-tenant-isolation-auditor.js.map +1 -0
- package/dist/agents/20-workflow-completion-tester.d.ts +12 -0
- package/dist/agents/20-workflow-completion-tester.d.ts.map +1 -0
- package/dist/agents/20-workflow-completion-tester.js +159 -0
- package/dist/agents/20-workflow-completion-tester.js.map +1 -0
- package/dist/agents/21-state-session-tester.d.ts +10 -0
- package/dist/agents/21-state-session-tester.d.ts.map +1 -0
- package/dist/agents/21-state-session-tester.js +233 -0
- package/dist/agents/21-state-session-tester.js.map +1 -0
- package/dist/agents/22-email-notification-verifier.d.ts +11 -0
- package/dist/agents/22-email-notification-verifier.d.ts.map +1 -0
- package/dist/agents/22-email-notification-verifier.js +199 -0
- package/dist/agents/22-email-notification-verifier.js.map +1 -0
- package/dist/agents/23-migration-tester.d.ts +10 -0
- package/dist/agents/23-migration-tester.d.ts.map +1 -0
- package/dist/agents/23-migration-tester.js +74 -0
- package/dist/agents/23-migration-tester.js.map +1 -0
- package/dist/agents/base-agent.d.ts +19 -0
- package/dist/agents/base-agent.d.ts.map +1 -0
- package/dist/agents/base-agent.js +67 -0
- package/dist/agents/base-agent.js.map +1 -0
- package/dist/agents/registry.d.ts +29 -0
- package/dist/agents/registry.d.ts.map +1 -0
- package/dist/agents/registry.js +117 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/core/ci-output.d.ts +35 -0
- package/dist/core/ci-output.d.ts.map +1 -0
- package/dist/core/ci-output.js +193 -0
- package/dist/core/ci-output.js.map +1 -0
- package/dist/core/cli.d.ts +11 -0
- package/dist/core/cli.d.ts.map +1 -0
- package/dist/core/cli.js +197 -0
- package/dist/core/cli.js.map +1 -0
- package/dist/core/config.d.ts +111 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +42 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/cost-tracker.d.ts +22 -0
- package/dist/core/cost-tracker.d.ts.map +1 -0
- package/dist/core/cost-tracker.js +41 -0
- package/dist/core/cost-tracker.js.map +1 -0
- package/dist/core/evidence.d.ts +28 -0
- package/dist/core/evidence.d.ts.map +1 -0
- package/dist/core/evidence.js +95 -0
- package/dist/core/evidence.js.map +1 -0
- package/dist/core/fix-loop.d.ts +29 -0
- package/dist/core/fix-loop.d.ts.map +1 -0
- package/dist/core/fix-loop.js +70 -0
- package/dist/core/fix-loop.js.map +1 -0
- package/dist/core/health-check.d.ts +21 -0
- package/dist/core/health-check.d.ts.map +1 -0
- package/dist/core/health-check.js +26 -0
- package/dist/core/health-check.js.map +1 -0
- package/dist/core/init.d.ts +2 -0
- package/dist/core/init.d.ts.map +1 -0
- package/dist/core/init.js +435 -0
- package/dist/core/init.js.map +1 -0
- package/dist/core/license-gen.d.ts +12 -0
- package/dist/core/license-gen.d.ts.map +1 -0
- package/dist/core/license-gen.js +169 -0
- package/dist/core/license-gen.js.map +1 -0
- package/dist/core/license.d.ts +33 -0
- package/dist/core/license.d.ts.map +1 -0
- package/dist/core/license.js +170 -0
- package/dist/core/license.js.map +1 -0
- package/dist/core/messages.d.ts +10 -0
- package/dist/core/messages.d.ts.map +1 -0
- package/dist/core/messages.js +47 -0
- package/dist/core/messages.js.map +1 -0
- package/dist/core/multi-browser.d.ts +36 -0
- package/dist/core/multi-browser.d.ts.map +1 -0
- package/dist/core/multi-browser.js +88 -0
- package/dist/core/multi-browser.js.map +1 -0
- package/dist/core/orchestrator.d.ts +48 -0
- package/dist/core/orchestrator.d.ts.map +1 -0
- package/dist/core/orchestrator.js +291 -0
- package/dist/core/orchestrator.js.map +1 -0
- package/dist/core/phase-gate.d.ts +4 -0
- package/dist/core/phase-gate.d.ts.map +1 -0
- package/dist/core/phase-gate.js +39 -0
- package/dist/core/phase-gate.js.map +1 -0
- package/dist/core/report-html.d.ts +9 -0
- package/dist/core/report-html.d.ts.map +1 -0
- package/dist/core/report-html.js +617 -0
- package/dist/core/report-html.js.map +1 -0
- package/dist/core/report-upload.d.ts +16 -0
- package/dist/core/report-upload.d.ts.map +1 -0
- package/dist/core/report-upload.js +124 -0
- package/dist/core/report-upload.js.map +1 -0
- package/dist/core/run-counter.d.ts +40 -0
- package/dist/core/run-counter.d.ts.map +1 -0
- package/dist/core/run-counter.js +120 -0
- package/dist/core/run-counter.js.map +1 -0
- package/dist/core/types.d.ts +53 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/helpers/api-client.d.ts +30 -0
- package/dist/helpers/api-client.d.ts.map +1 -0
- package/dist/helpers/api-client.js +77 -0
- package/dist/helpers/api-client.js.map +1 -0
- package/dist/helpers/element-discovery.d.ts +18 -0
- package/dist/helpers/element-discovery.d.ts.map +1 -0
- package/dist/helpers/element-discovery.js +82 -0
- package/dist/helpers/element-discovery.js.map +1 -0
- package/dist/helpers/env-resolver.d.ts +29 -0
- package/dist/helpers/env-resolver.d.ts.map +1 -0
- package/dist/helpers/env-resolver.js +51 -0
- package/dist/helpers/env-resolver.js.map +1 -0
- package/dist/helpers/form-filler.d.ts +13 -0
- package/dist/helpers/form-filler.d.ts.map +1 -0
- package/dist/helpers/form-filler.js +98 -0
- package/dist/helpers/form-filler.js.map +1 -0
- package/dist/helpers/modal-handler.d.ts +16 -0
- package/dist/helpers/modal-handler.d.ts.map +1 -0
- package/dist/helpers/modal-handler.js +95 -0
- package/dist/helpers/modal-handler.js.map +1 -0
- package/dist/helpers/navigation.d.ts +37 -0
- package/dist/helpers/navigation.d.ts.map +1 -0
- package/dist/helpers/navigation.js +83 -0
- package/dist/helpers/navigation.js.map +1 -0
- package/dist/helpers/quality-gate.d.ts +17 -0
- package/dist/helpers/quality-gate.d.ts.map +1 -0
- package/dist/helpers/quality-gate.js +144 -0
- package/dist/helpers/quality-gate.js.map +1 -0
- package/dist/helpers/screenshot.d.ts +24 -0
- package/dist/helpers/screenshot.d.ts.map +1 -0
- package/dist/helpers/screenshot.js +76 -0
- package/dist/helpers/screenshot.js.map +1 -0
- package/dist/helpers/seed-validator.d.ts +15 -0
- package/dist/helpers/seed-validator.d.ts.map +1 -0
- package/dist/helpers/seed-validator.js +53 -0
- package/dist/helpers/seed-validator.js.map +1 -0
- package/helpers/__tests__/api-client.test.ts +199 -0
- package/helpers/__tests__/element-discovery.test.ts +202 -0
- package/helpers/__tests__/form-filler-extended.test.ts +212 -0
- package/helpers/__tests__/form-filler.test.ts +99 -0
- package/helpers/__tests__/modal-handler.test.ts +152 -0
- package/helpers/__tests__/navigation.test.ts +214 -0
- package/helpers/__tests__/quality-gate.test.ts +117 -0
- package/helpers/__tests__/screenshot.test.ts +139 -0
- package/helpers/__tests__/seed-validator.test.ts +114 -0
- package/helpers/api-client.ts +111 -0
- package/helpers/element-discovery.ts +105 -0
- package/helpers/env-resolver.ts +69 -0
- package/helpers/form-filler.ts +126 -0
- package/helpers/modal-handler.ts +108 -0
- package/helpers/navigation.ts +100 -0
- package/helpers/quality-gate.ts +180 -0
- package/helpers/screenshot.ts +111 -0
- package/helpers/seed-validator.ts +70 -0
- package/package.json +88 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { BaseAgent } from './base-agent';
|
|
4
|
+
/** Regex to extract URLs from markdown link syntax [text](url) */
|
|
5
|
+
const MARKDOWN_LINK_REGEX = /\[.*?\]\((https?:\/\/[^)]+)\)/g;
|
|
6
|
+
/** Regex to extract bare URLs (not inside markdown link parens) */
|
|
7
|
+
const BARE_URL_REGEX = /(?<!\()(https?:\/\/[^\s"'<>)\]]+)/g;
|
|
8
|
+
/** Regex to find catch blocks that swallow errors with just a log */
|
|
9
|
+
const BARE_CATCH_LOG_REGEX = /catch\s*\([^)]*\)\s*\{[^}]*(?:console\.log|console\.error|logger\.\w+)\s*\(\s*(?:\w+\.message|error\.message)\s*\)[^}]*\}/g;
|
|
10
|
+
async function fileExists(p) {
|
|
11
|
+
try {
|
|
12
|
+
await stat(p);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function isDirectory(p) {
|
|
20
|
+
try {
|
|
21
|
+
return (await stat(p)).isDirectory();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class DocumentationValidatorAgent extends BaseAgent {
|
|
28
|
+
agentId = 17;
|
|
29
|
+
agentName = 'Documentation Validator';
|
|
30
|
+
async preFlight() {
|
|
31
|
+
const projectRoot = this.resolveProjectRoot();
|
|
32
|
+
const readmeExists = await fileExists(join(projectRoot, 'README.md'));
|
|
33
|
+
const docsExists = await fileExists(join(projectRoot, 'docs'));
|
|
34
|
+
if (!readmeExists && !docsExists) {
|
|
35
|
+
throw new Error('No README.md or docs/ directory found — nothing to validate');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async execute() {
|
|
39
|
+
const findings = [];
|
|
40
|
+
const projectRoot = this.resolveProjectRoot();
|
|
41
|
+
// 1. Collect all markdown files from project root and docs/
|
|
42
|
+
const markdownFiles = await this.collectMarkdownFiles(projectRoot);
|
|
43
|
+
// 2. Extract all URLs from markdown files
|
|
44
|
+
const urlSources = await this.extractUrlsFromFiles(markdownFiles);
|
|
45
|
+
// 3. HTTP HEAD each URL — create finding for 404/5xx
|
|
46
|
+
const brokenLinkFindings = await this.checkUrls(urlSources);
|
|
47
|
+
findings.push(...brokenLinkFindings);
|
|
48
|
+
// 4. Scan source code for catch blocks that only log error.message
|
|
49
|
+
const catchFindings = await this.scanForBareCatchLogs(projectRoot);
|
|
50
|
+
findings.push(...catchFindings);
|
|
51
|
+
return findings;
|
|
52
|
+
}
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Helpers
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the project root. The runDir is typically a subdirectory created
|
|
58
|
+
* for the current test run; the actual project root is the repo root.
|
|
59
|
+
* We walk upward from runDir until we find a package.json or use CWD.
|
|
60
|
+
*/
|
|
61
|
+
resolveProjectRoot() {
|
|
62
|
+
// Prefer process.cwd() — the test runner / CLI always sets CWD to repo root
|
|
63
|
+
return process.cwd();
|
|
64
|
+
}
|
|
65
|
+
/** Recursively collect .md files from projectRoot and any docs/ sub-directory. */
|
|
66
|
+
async collectMarkdownFiles(projectRoot) {
|
|
67
|
+
const files = [];
|
|
68
|
+
// Root-level .md files
|
|
69
|
+
try {
|
|
70
|
+
const rootEntries = await readdir(projectRoot);
|
|
71
|
+
for (const entry of rootEntries) {
|
|
72
|
+
if (entry.endsWith('.md')) {
|
|
73
|
+
files.push(join(projectRoot, entry));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Cannot read project root — return empty
|
|
79
|
+
return files;
|
|
80
|
+
}
|
|
81
|
+
// docs/ sub-directory (recursive)
|
|
82
|
+
const docsDir = join(projectRoot, 'docs');
|
|
83
|
+
const docsIsDir = await isDirectory(docsDir);
|
|
84
|
+
if (docsIsDir) {
|
|
85
|
+
await this.walkMarkdownFiles(docsDir, files);
|
|
86
|
+
}
|
|
87
|
+
return files;
|
|
88
|
+
}
|
|
89
|
+
async walkMarkdownFiles(dir, results) {
|
|
90
|
+
let entries;
|
|
91
|
+
try {
|
|
92
|
+
entries = await readdir(dir);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
for (const entry of entries) {
|
|
98
|
+
const fullPath = join(dir, entry);
|
|
99
|
+
try {
|
|
100
|
+
const s = await stat(fullPath);
|
|
101
|
+
if (s.isDirectory()) {
|
|
102
|
+
await this.walkMarkdownFiles(fullPath, results);
|
|
103
|
+
}
|
|
104
|
+
else if (entry.endsWith('.md')) {
|
|
105
|
+
results.push(fullPath);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Skip unreadable entries
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Extract all HTTP/HTTPS URLs from the given markdown files.
|
|
115
|
+
* Returns a map: url → source file path.
|
|
116
|
+
*/
|
|
117
|
+
async extractUrlsFromFiles(files) {
|
|
118
|
+
const urlMap = new Map();
|
|
119
|
+
for (const filePath of files) {
|
|
120
|
+
let content;
|
|
121
|
+
try {
|
|
122
|
+
content = await readFile(filePath, 'utf-8');
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
// Markdown link syntax
|
|
128
|
+
for (const match of content.matchAll(MARKDOWN_LINK_REGEX)) {
|
|
129
|
+
const url = match[1];
|
|
130
|
+
if (url && !urlMap.has(url)) {
|
|
131
|
+
urlMap.set(url, filePath);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Bare URLs not already captured via markdown links
|
|
135
|
+
for (const match of content.matchAll(BARE_URL_REGEX)) {
|
|
136
|
+
const url = match[0];
|
|
137
|
+
if (url && !urlMap.has(url)) {
|
|
138
|
+
urlMap.set(url, filePath);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return urlMap;
|
|
143
|
+
}
|
|
144
|
+
/** HEAD each URL; create a 'low' finding for 404 or 5xx responses. */
|
|
145
|
+
async checkUrls(urlSources) {
|
|
146
|
+
const findings = [];
|
|
147
|
+
let findingIndex = 0;
|
|
148
|
+
for (const [url, sourceFile] of urlSources) {
|
|
149
|
+
let status = null;
|
|
150
|
+
try {
|
|
151
|
+
const response = await fetch(url, {
|
|
152
|
+
method: 'HEAD',
|
|
153
|
+
signal: AbortSignal.timeout(10_000),
|
|
154
|
+
redirect: 'follow',
|
|
155
|
+
});
|
|
156
|
+
status = response.status;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Network error — treat as unreachable
|
|
160
|
+
status = null;
|
|
161
|
+
}
|
|
162
|
+
const isBroken = status === null ||
|
|
163
|
+
status === 404 ||
|
|
164
|
+
status >= 500;
|
|
165
|
+
if (isBroken) {
|
|
166
|
+
const statusLabel = status !== null ? String(status) : 'unreachable';
|
|
167
|
+
findings.push({
|
|
168
|
+
id: `${this.agentId}-broken-link-${findingIndex++}`,
|
|
169
|
+
type: 'test-bug',
|
|
170
|
+
severity: 'low',
|
|
171
|
+
agentId: this.agentId,
|
|
172
|
+
module: 'documentation',
|
|
173
|
+
description: `Broken link [${statusLabel}]: ${url}`,
|
|
174
|
+
file: sourceFile,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return findings;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Scan TypeScript / JavaScript source files for catch blocks that only log
|
|
182
|
+
* error.message without providing a user-friendly message.
|
|
183
|
+
*/
|
|
184
|
+
async scanForBareCatchLogs(projectRoot) {
|
|
185
|
+
const findings = [];
|
|
186
|
+
const srcDirs = ['src', 'apps', 'libs', 'packages'];
|
|
187
|
+
for (const dir of srcDirs) {
|
|
188
|
+
const fullDir = join(projectRoot, dir);
|
|
189
|
+
const dirExists = await fileExists(fullDir);
|
|
190
|
+
if (!dirExists)
|
|
191
|
+
continue;
|
|
192
|
+
const sourceFiles = [];
|
|
193
|
+
await this.walkSourceFiles(fullDir, sourceFiles);
|
|
194
|
+
for (const filePath of sourceFiles) {
|
|
195
|
+
let content;
|
|
196
|
+
try {
|
|
197
|
+
content = await readFile(filePath, 'utf-8');
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const matches = content.match(BARE_CATCH_LOG_REGEX);
|
|
203
|
+
if (matches && matches.length > 0) {
|
|
204
|
+
findings.push({
|
|
205
|
+
id: `${this.agentId}-bare-catch-${filePath.replace(/[^a-z0-9]/gi, '-').slice(-60)}`,
|
|
206
|
+
type: 'code-bug-logic',
|
|
207
|
+
severity: 'low',
|
|
208
|
+
agentId: this.agentId,
|
|
209
|
+
module: 'error-handling',
|
|
210
|
+
description: `${matches.length} catch block(s) in "${filePath}" only log error.message without a user-friendly message`,
|
|
211
|
+
file: filePath,
|
|
212
|
+
suggestedFix: 'Replace bare error.message logs with structured error responses that include user-friendly messages',
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return findings;
|
|
218
|
+
}
|
|
219
|
+
async walkSourceFiles(dir, results) {
|
|
220
|
+
let entries;
|
|
221
|
+
try {
|
|
222
|
+
entries = await readdir(dir);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
for (const entry of entries) {
|
|
228
|
+
if (entry === 'node_modules' || entry === 'dist' || entry === '.git')
|
|
229
|
+
continue;
|
|
230
|
+
const fullPath = join(dir, entry);
|
|
231
|
+
try {
|
|
232
|
+
const s = await stat(fullPath);
|
|
233
|
+
if (s.isDirectory()) {
|
|
234
|
+
await this.walkSourceFiles(fullPath, results);
|
|
235
|
+
}
|
|
236
|
+
else if (entry.endsWith('.ts') || entry.endsWith('.js')) {
|
|
237
|
+
results.push(fullPath);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// Skip unreadable entries
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=17-documentation-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"17-documentation-validator.js","sourceRoot":"","sources":["../../agents/17-documentation-validator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseAgent } from './base-agent';
|
|
2
|
+
import type { Finding } from '../core/types';
|
|
3
|
+
export declare class IntegrationWatchdogAgent extends BaseAgent {
|
|
4
|
+
readonly agentId = 18;
|
|
5
|
+
readonly agentName = "Integration Watchdog";
|
|
6
|
+
private repoRoot;
|
|
7
|
+
protected preFlight(): Promise<void>;
|
|
8
|
+
protected execute(): Promise<Finding[]>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=18-integration-watchdog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"18-integration-watchdog.d.ts","sourceRoot":"","sources":["../../agents/18-integration-watchdog.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AA4B7C,qBAAa,wBAAyB,SAAQ,SAAS;IACrD,QAAQ,CAAC,OAAO,MAAM;IACtB,QAAQ,CAAC,SAAS,0BAA0B;IAC5C,OAAO,CAAC,QAAQ,CAAM;cAEN,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;cAgB1B,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;CA4H9C"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { BaseAgent } from './base-agent';
|
|
5
|
+
function runVitest(vitestConfigPath, cwd) {
|
|
6
|
+
return new Promise((resolveP, rejectP) => {
|
|
7
|
+
execFile('npx', ['vitest', 'run', '--config', vitestConfigPath], { cwd, shell: true }, (err, stdout, stderr) => {
|
|
8
|
+
if (err) {
|
|
9
|
+
const enriched = Object.assign(new Error(err.message), {
|
|
10
|
+
stdout: stdout ?? '',
|
|
11
|
+
stderr: stderr ?? '',
|
|
12
|
+
});
|
|
13
|
+
rejectP(enriched);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
resolveP({ stdout: stdout ?? '', stderr: stderr ?? '' });
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export class IntegrationWatchdogAgent extends BaseAgent {
|
|
21
|
+
agentId = 18;
|
|
22
|
+
agentName = 'Integration Watchdog';
|
|
23
|
+
repoRoot = '';
|
|
24
|
+
async preFlight() {
|
|
25
|
+
this.repoRoot = this.config.projectRoot ?? process.cwd();
|
|
26
|
+
if (this.config.integrations.length === 0) {
|
|
27
|
+
throw new Error('IntegrationWatchdogAgent requires at least one integration in config.integrations');
|
|
28
|
+
}
|
|
29
|
+
// Verify worker directories exist
|
|
30
|
+
const workersDir = resolve(this.repoRoot, 'apps', 'workers');
|
|
31
|
+
if (!existsSync(workersDir)) {
|
|
32
|
+
throw new Error(`Worker directory not found: ${workersDir}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async execute() {
|
|
36
|
+
const findings = [];
|
|
37
|
+
// -----------------------------------------------------------------------
|
|
38
|
+
// Worker coverage checks
|
|
39
|
+
// -----------------------------------------------------------------------
|
|
40
|
+
for (const worker of this.config.workers) {
|
|
41
|
+
const workerName = worker.name;
|
|
42
|
+
// Check source file exists
|
|
43
|
+
const workerSrcCandidates = [
|
|
44
|
+
resolve(this.repoRoot, 'apps', 'workers', 'src', `${workerName}.ts`),
|
|
45
|
+
resolve(this.repoRoot, 'apps', 'workers', 'src', workerName, 'index.ts'),
|
|
46
|
+
resolve(this.repoRoot, 'apps', 'workers', `${workerName}.ts`),
|
|
47
|
+
];
|
|
48
|
+
const workerSrcExists = workerSrcCandidates.some((p) => existsSync(p));
|
|
49
|
+
if (!workerSrcExists) {
|
|
50
|
+
findings.push({
|
|
51
|
+
id: `${this.agentId}-worker-missing-${workerName}`,
|
|
52
|
+
type: 'infra-issue',
|
|
53
|
+
severity: 'medium',
|
|
54
|
+
agentId: this.agentId,
|
|
55
|
+
module: `worker:${workerName}`,
|
|
56
|
+
description: `Worker source file not found for "${workerName}". ` +
|
|
57
|
+
`Searched: ${workerSrcCandidates.join(', ')}`,
|
|
58
|
+
suggestedFix: `Create the worker source file at apps/workers/src/${workerName}.ts`,
|
|
59
|
+
});
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
// Check test files exist
|
|
63
|
+
const workerTestCandidates = [
|
|
64
|
+
resolve(this.repoRoot, 'apps', 'workers', 'src', `${workerName}.test.ts`),
|
|
65
|
+
resolve(this.repoRoot, 'apps', 'workers', 'src', `${workerName}.spec.ts`),
|
|
66
|
+
resolve(this.repoRoot, 'apps', 'workers', '__tests__', `${workerName}.test.ts`),
|
|
67
|
+
resolve(this.repoRoot, 'apps', 'workers', 'src', workerName, `${workerName}.test.ts`),
|
|
68
|
+
];
|
|
69
|
+
const workerTestExists = workerTestCandidates.some((p) => existsSync(p));
|
|
70
|
+
if (!workerTestExists) {
|
|
71
|
+
findings.push({
|
|
72
|
+
id: `${this.agentId}-worker-no-tests-${workerName}`,
|
|
73
|
+
type: 'test-bug',
|
|
74
|
+
severity: 'medium',
|
|
75
|
+
agentId: this.agentId,
|
|
76
|
+
module: `worker:${workerName}`,
|
|
77
|
+
description: `No test file found for worker "${workerName}".`,
|
|
78
|
+
suggestedFix: `Add tests at apps/workers/src/${workerName}.test.ts`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// -----------------------------------------------------------------------
|
|
83
|
+
// Run worker tests if workers config and vitest config exist
|
|
84
|
+
// -----------------------------------------------------------------------
|
|
85
|
+
if (this.config.workers.length > 0) {
|
|
86
|
+
const vitestConfig = resolve(this.repoRoot, 'apps', 'workers', 'vitest.config.ts');
|
|
87
|
+
if (existsSync(vitestConfig)) {
|
|
88
|
+
try {
|
|
89
|
+
await runVitest(vitestConfig, this.repoRoot);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
const stderr = error instanceof Error && 'stderr' in error
|
|
93
|
+
? String(error.stderr)
|
|
94
|
+
: String(error);
|
|
95
|
+
const stdout = error instanceof Error && 'stdout' in error
|
|
96
|
+
? String(error.stdout)
|
|
97
|
+
: '';
|
|
98
|
+
findings.push({
|
|
99
|
+
id: `${this.agentId}-worker-tests-failed`,
|
|
100
|
+
type: 'test-bug',
|
|
101
|
+
severity: 'high',
|
|
102
|
+
agentId: this.agentId,
|
|
103
|
+
module: 'workers',
|
|
104
|
+
description: `Worker test suite failed.\n${stderr || stdout}`,
|
|
105
|
+
suggestedFix: 'Fix the failing worker tests before promoting to UAT.',
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// -----------------------------------------------------------------------
|
|
111
|
+
// Integration sandbox checks
|
|
112
|
+
// -----------------------------------------------------------------------
|
|
113
|
+
for (const integration of this.config.integrations) {
|
|
114
|
+
const integrationName = integration.name;
|
|
115
|
+
// Detect sandbox availability — look for connection config
|
|
116
|
+
const connectionConfigCandidates = [
|
|
117
|
+
resolve(this.repoRoot, 'apps', 'workers', 'src', 'integrations', `${integrationName}.ts`),
|
|
118
|
+
resolve(this.repoRoot, 'apps', 'api', 'src', 'integrations', `${integrationName}.ts`),
|
|
119
|
+
resolve(this.repoRoot, 'apps', 'workers', 'src', 'integrations', `${integrationName}-sandbox.ts`),
|
|
120
|
+
];
|
|
121
|
+
const sandboxAvailable = connectionConfigCandidates.some((p) => existsSync(p));
|
|
122
|
+
if (!sandboxAvailable) {
|
|
123
|
+
findings.push({
|
|
124
|
+
id: `${this.agentId}-integration-no-sandbox-${integrationName}`,
|
|
125
|
+
type: 'infra-issue',
|
|
126
|
+
severity: 'low',
|
|
127
|
+
agentId: this.agentId,
|
|
128
|
+
module: `integration:${integrationName}`,
|
|
129
|
+
description: `Advisory: No sandbox connection config found for integration "${integrationName}". ` +
|
|
130
|
+
`End-to-end integration testing cannot be performed automatically.`,
|
|
131
|
+
suggestedFix: `Add a sandbox configuration at apps/workers/src/integrations/${integrationName}.ts`,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return findings;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=18-integration-watchdog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"18-integration-watchdog.js","sourceRoot":"","sources":["../../agents/18-integration-watchdog.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Finding } from '../core/types';
|
|
2
|
+
import { BaseAgent } from './base-agent';
|
|
3
|
+
export declare class TenantIsolationAuditorAgent extends BaseAgent {
|
|
4
|
+
readonly agentId = 19;
|
|
5
|
+
readonly agentName = "Tenant Isolation Auditor";
|
|
6
|
+
protected preFlight(): Promise<void>;
|
|
7
|
+
protected execute(): Promise<Finding[]>;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=19-tenant-isolation-auditor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"19-tenant-isolation-auditor.d.ts","sourceRoot":"","sources":["../../agents/19-tenant-isolation-auditor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAIzC,qBAAa,2BAA4B,SAAQ,SAAS;IACxD,QAAQ,CAAC,OAAO,MAAM;IACtB,QAAQ,CAAC,SAAS,8BAA8B;cAEhC,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;cAc1B,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;CA+K9C"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { BaseAgent } from './base-agent';
|
|
2
|
+
import { ApiClient } from '../helpers/api-client';
|
|
3
|
+
import { resolveEnvironment } from '../helpers/env-resolver';
|
|
4
|
+
export class TenantIsolationAuditorAgent extends BaseAgent {
|
|
5
|
+
agentId = 19;
|
|
6
|
+
agentName = 'Tenant Isolation Auditor';
|
|
7
|
+
async preFlight() {
|
|
8
|
+
if (!this.config.tenancy.enabled) {
|
|
9
|
+
throw new Error('TenantIsolationAuditorAgent requires config.tenancy.enabled to be true');
|
|
10
|
+
}
|
|
11
|
+
if (this.config.tenancy.testFirms.length < 1) {
|
|
12
|
+
throw new Error('TenantIsolationAuditorAgent requires at least one firm in config.tenancy.testFirms');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async execute() {
|
|
16
|
+
const findings = [];
|
|
17
|
+
const { env, fallback } = await resolveEnvironment(this.config, this.phase);
|
|
18
|
+
if (!env) {
|
|
19
|
+
findings.push({
|
|
20
|
+
id: `${this.agentId}-no-env`,
|
|
21
|
+
type: 'infra-issue',
|
|
22
|
+
severity: 'critical',
|
|
23
|
+
agentId: this.agentId,
|
|
24
|
+
module: 'tenancy',
|
|
25
|
+
description: 'No environment configured — cannot run tenant isolation audit',
|
|
26
|
+
});
|
|
27
|
+
return findings;
|
|
28
|
+
}
|
|
29
|
+
const credentials = this.config.auth.credentials?.['admin'] ??
|
|
30
|
+
Object.values(this.config.auth.credentials ?? {})[0];
|
|
31
|
+
if (!credentials) {
|
|
32
|
+
findings.push({
|
|
33
|
+
id: `${this.agentId}-no-credentials`,
|
|
34
|
+
type: 'infra-issue',
|
|
35
|
+
severity: 'critical',
|
|
36
|
+
agentId: this.agentId,
|
|
37
|
+
module: 'tenancy',
|
|
38
|
+
description: 'No credentials configured — cannot authenticate for tenant isolation audit',
|
|
39
|
+
});
|
|
40
|
+
return findings;
|
|
41
|
+
}
|
|
42
|
+
const firmA = this.config.tenancy.testFirms[0];
|
|
43
|
+
const firmB = this.config.tenancy.testFirms.length > 1 ? this.config.tenancy.testFirms[1] : null;
|
|
44
|
+
const isolationField = this.config.tenancy.isolationField;
|
|
45
|
+
let client;
|
|
46
|
+
try {
|
|
47
|
+
client = await ApiClient.createAuthenticated(env.baseUrl, credentials);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
// Auth failure is always medium — indicates missing API or wrong credentials, not a tenant isolation bug
|
|
51
|
+
const authSeverity = 'medium';
|
|
52
|
+
findings.push({
|
|
53
|
+
id: `${this.agentId}-auth-failed`,
|
|
54
|
+
type: 'infra-issue',
|
|
55
|
+
severity: authSeverity,
|
|
56
|
+
agentId: this.agentId,
|
|
57
|
+
module: 'tenancy',
|
|
58
|
+
description: `Authentication failed for tenant isolation audit: ${error instanceof Error ? error.message : String(error)}`,
|
|
59
|
+
});
|
|
60
|
+
return findings;
|
|
61
|
+
}
|
|
62
|
+
// -----------------------------------------------------------------------
|
|
63
|
+
// Test 1: URL tampering — access resources with wrong firmId in URL
|
|
64
|
+
// -----------------------------------------------------------------------
|
|
65
|
+
const urlTamperPaths = [
|
|
66
|
+
`/api/clients?${isolationField}=${firmA}`,
|
|
67
|
+
`/api/invoices?${isolationField}=${firmA}`,
|
|
68
|
+
`/api/transactions?${isolationField}=${firmA}`,
|
|
69
|
+
];
|
|
70
|
+
// Use firmB if available, otherwise try a synthetic non-existent firm
|
|
71
|
+
const wrongFirm = firmB ?? 'wrong-firm-99999';
|
|
72
|
+
for (const basePath of urlTamperPaths) {
|
|
73
|
+
const tamperedPath = basePath.replace(`${isolationField}=${firmA}`, `${isolationField}=${wrongFirm}`);
|
|
74
|
+
let response;
|
|
75
|
+
try {
|
|
76
|
+
response = await client.get(tamperedPath);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Network or parse error — skip this path
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (response.status === 200) {
|
|
83
|
+
const hasData = Array.isArray(response.body)
|
|
84
|
+
? response.body.length > 0
|
|
85
|
+
: response.body !== null && typeof response.body === 'object';
|
|
86
|
+
if (hasData) {
|
|
87
|
+
findings.push({
|
|
88
|
+
id: `${this.agentId}-url-tamper-${tamperedPath.replace(/[^a-zA-Z0-9]/g, '-')}`,
|
|
89
|
+
type: 'code-bug-security',
|
|
90
|
+
severity: 'critical',
|
|
91
|
+
agentId: this.agentId,
|
|
92
|
+
module: 'tenancy',
|
|
93
|
+
description: `URL tampering data leak: GET ${tamperedPath} returned 200 with data while authenticated as firm "${firmA}". ` +
|
|
94
|
+
`Firm "${wrongFirm}" data should not be accessible.`,
|
|
95
|
+
suggestedFix: `Enforce tenant scoping in all query handlers — always filter by the authenticated user's ${isolationField}, ignoring URL parameters.`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// -----------------------------------------------------------------------
|
|
101
|
+
// Test 2: API parameter tampering — POST with different firmId
|
|
102
|
+
// -----------------------------------------------------------------------
|
|
103
|
+
const postPaths = ['/api/clients/search', '/api/invoices/search', '/api/reports/generate'];
|
|
104
|
+
for (const path of postPaths) {
|
|
105
|
+
let response;
|
|
106
|
+
try {
|
|
107
|
+
response = await client.post(path, { [isolationField]: wrongFirm, limit: 5 });
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (response.status === 200) {
|
|
113
|
+
const hasData = Array.isArray(response.body)
|
|
114
|
+
? response.body.length > 0
|
|
115
|
+
: response.body !== null && typeof response.body === 'object';
|
|
116
|
+
if (hasData) {
|
|
117
|
+
findings.push({
|
|
118
|
+
id: `${this.agentId}-param-tamper-${path.replace(/\//g, '-')}`,
|
|
119
|
+
type: 'code-bug-security',
|
|
120
|
+
severity: 'critical',
|
|
121
|
+
agentId: this.agentId,
|
|
122
|
+
module: 'tenancy',
|
|
123
|
+
description: `API parameter tampering data leak: POST ${path} with ${isolationField}=${wrongFirm} returned 200 with data ` +
|
|
124
|
+
`while authenticated as firm "${firmA}".`,
|
|
125
|
+
suggestedFix: `Never trust ${isolationField} from the request body — always derive it from the authenticated user's JWT claims.`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// -----------------------------------------------------------------------
|
|
131
|
+
// Test 3: Search isolation — search for firm B data while logged in as firm A
|
|
132
|
+
// -----------------------------------------------------------------------
|
|
133
|
+
if (firmB !== null) {
|
|
134
|
+
const searchPaths = [
|
|
135
|
+
{ path: `/api/clients/search`, body: { query: firmB, limit: 5 } },
|
|
136
|
+
{ path: `/api/invoices/search`, body: { query: firmB, limit: 5 } },
|
|
137
|
+
];
|
|
138
|
+
for (const { path, body } of searchPaths) {
|
|
139
|
+
let response;
|
|
140
|
+
try {
|
|
141
|
+
response = await client.post(path, body);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (response.status === 200) {
|
|
147
|
+
const results = Array.isArray(response.body) ? response.body : [];
|
|
148
|
+
if (results.length > 0) {
|
|
149
|
+
findings.push({
|
|
150
|
+
id: `${this.agentId}-search-isolation-${path.replace(/\//g, '-')}`,
|
|
151
|
+
type: 'code-bug-security',
|
|
152
|
+
severity: 'critical',
|
|
153
|
+
agentId: this.agentId,
|
|
154
|
+
module: 'tenancy',
|
|
155
|
+
description: `Search isolation failure: POST ${path} with query for firm "${firmB}" ` +
|
|
156
|
+
`returned ${results.length} result(s) while authenticated as firm "${firmA}".`,
|
|
157
|
+
suggestedFix: 'All search handlers must apply tenant scoping before executing the query.',
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return findings;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=19-tenant-isolation-auditor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"19-tenant-isolation-auditor.js","sourceRoot":"","sources":["../../agents/19-tenant-isolation-auditor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Finding } from '../core/types';
|
|
2
|
+
import { BaseAgent } from './base-agent';
|
|
3
|
+
export declare class WorkflowCompletionTesterAgent extends BaseAgent {
|
|
4
|
+
readonly agentId = 20;
|
|
5
|
+
readonly agentName = "Workflow Completion Tester";
|
|
6
|
+
private baseUrl;
|
|
7
|
+
protected preFlight(): Promise<void>;
|
|
8
|
+
protected execute(): Promise<Finding[]>;
|
|
9
|
+
private runWorkflow;
|
|
10
|
+
private executeStep;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=20-workflow-completion-tester.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20-workflow-completion-tester.d.ts","sourceRoot":"","sources":["../../agents/20-workflow-completion-tester.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAMzC,qBAAa,6BAA8B,SAAQ,SAAS;IAC1D,QAAQ,CAAC,OAAO,MAAM;IACtB,QAAQ,CAAC,SAAS,gCAAgC;IAClD,OAAO,CAAC,OAAO,CAAM;cAEL,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;cAW1B,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YAiE/B,WAAW;YA8DX,WAAW;CAkD1B"}
|