@avi770/testteam 1.2.0 → 3.0.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 +53 -1
- package/README.md +72 -7
- package/agents/15-regression-sentinel.ts +3 -2
- package/agents/24-signup-onboarding-tester.ts +429 -0
- package/agents/25-crud-flow-tester.ts +302 -0
- package/agents/26-form-validator.ts +297 -0
- package/agents/27-search-filter-tester.ts +326 -0
- package/agents/28-navigation-routing-tester.ts +425 -0
- package/agents/29-responsive-interaction-tester.ts +350 -0
- package/agents/30-multi-user-scenario-tester.ts +319 -0
- package/agents/31-load-tester.ts +134 -0
- package/agents/32-memory-leak-detector.ts +194 -0
- package/agents/33-bundle-analyzer.ts +132 -0
- package/agents/34-xss-scanner.ts +191 -0
- package/agents/35-csrf-tester.ts +82 -0
- package/agents/36-auth-fuzzer.ts +194 -0
- package/agents/37-dependency-scanner.ts +176 -0
- package/agents/38-secrets-scanner.ts +137 -0
- package/agents/39-api-contract-tester.ts +199 -0
- package/agents/40-rate-limit-tester.ts +94 -0
- package/agents/41-api-pagination-tester.ts +97 -0
- package/agents/42-graphql-tester.ts +222 -0
- package/agents/43-data-consistency-checker.ts +205 -0
- package/agents/44-backup-recovery-tester.ts +152 -0
- package/agents/45-data-privacy-scanner.ts +125 -0
- package/agents/46-seo-auditor.ts +294 -0
- package/agents/47-social-preview-tester.ts +232 -0
- package/agents/48-lighthouse-auditor.ts +213 -0
- package/agents/49-i18n-tester.ts +198 -0
- package/agents/50-timezone-tester.ts +173 -0
- package/agents/51-error-recovery-tester.ts +155 -0
- package/agents/52-offline-mode-tester.ts +180 -0
- package/agents/53-graceful-degradation-tester.ts +156 -0
- package/agents/54-websocket-tester.ts +151 -0
- package/agents/55-realtime-sync-tester.ts +194 -0
- package/agents/56-file-upload-tester.ts +194 -0
- package/agents/57-export-tester.ts +174 -0
- package/agents/58-payment-flow-tester.ts +183 -0
- package/agents/59-ssl-tls-auditor.ts +141 -0
- package/agents/60-dns-cdn-tester.ts +117 -0
- package/agents/61-docker-health-checker.ts +111 -0
- package/agents/62-env-config-validator.ts +152 -0
- package/agents/63-log-quality-auditor.ts +136 -0
- package/agents/64-analytics-tracker-tester.ts +165 -0
- package/agents/65-gdpr-compliance-tester.ts +215 -0
- package/agents/66-soc2-control-validator.ts +210 -0
- package/agents/67-wcag-aaa-tester.ts +241 -0
- package/agents/68-dead-code-detector.ts +135 -0
- package/agents/69-type-safety-auditor.ts +164 -0
- package/agents/70-complexity-analyzer.ts +179 -0
- package/agents/__tests__/24-signup-onboarding-tester.test.ts +274 -0
- package/agents/__tests__/25-crud-flow-tester.test.ts +322 -0
- package/agents/__tests__/26-form-validator.test.ts +345 -0
- package/agents/__tests__/27-search-filter-tester.test.ts +311 -0
- package/agents/__tests__/28-navigation-routing-tester.test.ts +328 -0
- package/agents/__tests__/29-responsive-interaction-tester.test.ts +297 -0
- package/agents/__tests__/30-multi-user-scenario-tester.test.ts +328 -0
- package/agents/__tests__/31-load-tester.test.ts +189 -0
- package/agents/__tests__/32-memory-leak-detector.test.ts +251 -0
- package/agents/__tests__/33-bundle-analyzer.test.ts +237 -0
- package/agents/__tests__/34-xss-scanner.test.ts +258 -0
- package/agents/__tests__/35-csrf-tester.test.ts +200 -0
- package/agents/__tests__/36-auth-fuzzer.test.ts +214 -0
- package/agents/__tests__/37-dependency-scanner.test.ts +266 -0
- package/agents/__tests__/38-secrets-scanner.test.ts +224 -0
- package/agents/__tests__/39-api-contract-tester.test.ts +312 -0
- package/agents/__tests__/40-rate-limit-tester.test.ts +192 -0
- package/agents/__tests__/41-api-pagination-tester.test.ts +198 -0
- package/agents/__tests__/42-graphql-tester.test.ts +252 -0
- package/agents/__tests__/43-data-consistency-checker.test.ts +232 -0
- package/agents/__tests__/44-backup-recovery-tester.test.ts +222 -0
- package/agents/__tests__/45-data-privacy-scanner.test.ts +223 -0
- package/agents/__tests__/46-seo-auditor.test.ts +261 -0
- package/agents/__tests__/47-social-preview-tester.test.ts +245 -0
- package/agents/__tests__/48-lighthouse-auditor.test.ts +276 -0
- package/agents/__tests__/49-i18n-tester.test.ts +201 -0
- package/agents/__tests__/50-timezone-tester.test.ts +172 -0
- package/agents/__tests__/51-error-recovery-tester.test.ts +162 -0
- package/agents/__tests__/52-offline-mode-tester.test.ts +164 -0
- package/agents/__tests__/53-graceful-degradation-tester.test.ts +168 -0
- package/agents/__tests__/54-websocket-tester.test.ts +157 -0
- package/agents/__tests__/55-realtime-sync-tester.test.ts +181 -0
- package/agents/__tests__/56-file-upload-tester.test.ts +172 -0
- package/agents/__tests__/57-export-tester.test.ts +169 -0
- package/agents/__tests__/58-payment-flow-tester.test.ts +182 -0
- package/agents/__tests__/59-ssl-tls-auditor.test.ts +179 -0
- package/agents/__tests__/60-dns-cdn-tester.test.ts +176 -0
- package/agents/__tests__/61-docker-health-checker.test.ts +150 -0
- package/agents/__tests__/62-env-config-validator.test.ts +166 -0
- package/agents/__tests__/63-log-quality-auditor.test.ts +175 -0
- package/agents/__tests__/64-analytics-tracker-tester.test.ts +158 -0
- package/agents/__tests__/65-gdpr-compliance-tester.test.ts +174 -0
- package/agents/__tests__/66-soc2-control-validator.test.ts +183 -0
- package/agents/__tests__/67-wcag-aaa-tester.test.ts +190 -0
- package/agents/__tests__/68-dead-code-detector.test.ts +174 -0
- package/agents/__tests__/69-type-safety-auditor.test.ts +173 -0
- package/agents/__tests__/70-complexity-analyzer.test.ts +177 -0
- package/agents/__tests__/registry.test.ts +13 -13
- package/agents/base-agent.ts +8 -0
- package/agents/registry.ts +171 -28
- package/core/__tests__/integration.test.ts +4 -4
- package/core/__tests__/orchestrator.test.ts +17 -16
- package/core/cli.ts +128 -6
- package/core/license.ts +208 -211
- package/core/orchestrator.ts +78 -5
- package/dist/agents/15-regression-sentinel.d.ts +2 -1
- package/dist/agents/15-regression-sentinel.d.ts.map +1 -1
- package/dist/agents/15-regression-sentinel.js +2 -2
- package/dist/agents/15-regression-sentinel.js.map +1 -1
- package/dist/agents/24-signup-onboarding-tester.d.ts +35 -0
- package/dist/agents/24-signup-onboarding-tester.d.ts.map +1 -0
- package/dist/agents/24-signup-onboarding-tester.js +357 -0
- package/dist/agents/24-signup-onboarding-tester.js.map +1 -0
- package/dist/agents/25-crud-flow-tester.d.ts +11 -0
- package/dist/agents/25-crud-flow-tester.d.ts.map +1 -0
- package/dist/agents/25-crud-flow-tester.js +253 -0
- package/dist/agents/25-crud-flow-tester.js.map +1 -0
- package/dist/agents/26-form-validator.d.ts +12 -0
- package/dist/agents/26-form-validator.d.ts.map +1 -0
- package/dist/agents/26-form-validator.js +257 -0
- package/dist/agents/26-form-validator.js.map +1 -0
- package/dist/agents/27-search-filter-tester.d.ts +20 -0
- package/dist/agents/27-search-filter-tester.d.ts.map +1 -0
- package/dist/agents/27-search-filter-tester.js +267 -0
- package/dist/agents/27-search-filter-tester.js.map +1 -0
- package/dist/agents/28-navigation-routing-tester.d.ts +32 -0
- package/dist/agents/28-navigation-routing-tester.d.ts.map +1 -0
- package/dist/agents/28-navigation-routing-tester.js +363 -0
- package/dist/agents/28-navigation-routing-tester.js.map +1 -0
- package/dist/agents/29-responsive-interaction-tester.d.ts +26 -0
- package/dist/agents/29-responsive-interaction-tester.d.ts.map +1 -0
- package/dist/agents/29-responsive-interaction-tester.js +272 -0
- package/dist/agents/29-responsive-interaction-tester.js.map +1 -0
- package/dist/agents/30-multi-user-scenario-tester.d.ts +24 -0
- package/dist/agents/30-multi-user-scenario-tester.d.ts.map +1 -0
- package/dist/agents/30-multi-user-scenario-tester.js +254 -0
- package/dist/agents/30-multi-user-scenario-tester.js.map +1 -0
- package/dist/agents/31-load-tester.d.ts +12 -0
- package/dist/agents/31-load-tester.d.ts.map +1 -0
- package/dist/agents/31-load-tester.js +110 -0
- package/dist/agents/31-load-tester.js.map +1 -0
- package/dist/agents/32-memory-leak-detector.d.ts +12 -0
- package/dist/agents/32-memory-leak-detector.d.ts.map +1 -0
- package/dist/agents/32-memory-leak-detector.js +167 -0
- package/dist/agents/32-memory-leak-detector.js.map +1 -0
- package/dist/agents/33-bundle-analyzer.d.ts +10 -0
- package/dist/agents/33-bundle-analyzer.d.ts.map +1 -0
- package/dist/agents/33-bundle-analyzer.js +111 -0
- package/dist/agents/33-bundle-analyzer.js.map +1 -0
- package/dist/agents/34-xss-scanner.d.ts +11 -0
- package/dist/agents/34-xss-scanner.d.ts.map +1 -0
- package/dist/agents/34-xss-scanner.js +164 -0
- package/dist/agents/34-xss-scanner.js.map +1 -0
- package/dist/agents/35-csrf-tester.d.ts +11 -0
- package/dist/agents/35-csrf-tester.d.ts.map +1 -0
- package/dist/agents/35-csrf-tester.js +70 -0
- package/dist/agents/35-csrf-tester.js.map +1 -0
- package/dist/agents/36-auth-fuzzer.d.ts +13 -0
- package/dist/agents/36-auth-fuzzer.d.ts.map +1 -0
- package/dist/agents/36-auth-fuzzer.js +163 -0
- package/dist/agents/36-auth-fuzzer.js.map +1 -0
- package/dist/agents/37-dependency-scanner.d.ts +11 -0
- package/dist/agents/37-dependency-scanner.d.ts.map +1 -0
- package/dist/agents/37-dependency-scanner.js +139 -0
- package/dist/agents/37-dependency-scanner.js.map +1 -0
- package/dist/agents/38-secrets-scanner.d.ts +11 -0
- package/dist/agents/38-secrets-scanner.d.ts.map +1 -0
- package/dist/agents/38-secrets-scanner.js +116 -0
- package/dist/agents/38-secrets-scanner.js.map +1 -0
- package/dist/agents/39-api-contract-tester.d.ts +12 -0
- package/dist/agents/39-api-contract-tester.d.ts.map +1 -0
- package/dist/agents/39-api-contract-tester.js +142 -0
- package/dist/agents/39-api-contract-tester.js.map +1 -0
- package/dist/agents/40-rate-limit-tester.d.ts +12 -0
- package/dist/agents/40-rate-limit-tester.d.ts.map +1 -0
- package/dist/agents/40-rate-limit-tester.js +79 -0
- package/dist/agents/40-rate-limit-tester.js.map +1 -0
- package/dist/agents/41-api-pagination-tester.d.ts +12 -0
- package/dist/agents/41-api-pagination-tester.d.ts.map +1 -0
- package/dist/agents/41-api-pagination-tester.js +79 -0
- package/dist/agents/41-api-pagination-tester.js.map +1 -0
- package/dist/agents/42-graphql-tester.d.ts +13 -0
- package/dist/agents/42-graphql-tester.d.ts.map +1 -0
- package/dist/agents/42-graphql-tester.js +187 -0
- package/dist/agents/42-graphql-tester.js.map +1 -0
- package/dist/agents/43-data-consistency-checker.d.ts +11 -0
- package/dist/agents/43-data-consistency-checker.d.ts.map +1 -0
- package/dist/agents/43-data-consistency-checker.js +176 -0
- package/dist/agents/43-data-consistency-checker.js.map +1 -0
- package/dist/agents/44-backup-recovery-tester.d.ts +11 -0
- package/dist/agents/44-backup-recovery-tester.d.ts.map +1 -0
- package/dist/agents/44-backup-recovery-tester.js +128 -0
- package/dist/agents/44-backup-recovery-tester.js.map +1 -0
- package/dist/agents/45-data-privacy-scanner.d.ts +11 -0
- package/dist/agents/45-data-privacy-scanner.d.ts.map +1 -0
- package/dist/agents/45-data-privacy-scanner.js +100 -0
- package/dist/agents/45-data-privacy-scanner.js.map +1 -0
- package/dist/agents/46-seo-auditor.d.ts +12 -0
- package/dist/agents/46-seo-auditor.d.ts.map +1 -0
- package/dist/agents/46-seo-auditor.js +275 -0
- package/dist/agents/46-seo-auditor.js.map +1 -0
- package/dist/agents/47-social-preview-tester.d.ts +11 -0
- package/dist/agents/47-social-preview-tester.d.ts.map +1 -0
- package/dist/agents/47-social-preview-tester.js +219 -0
- package/dist/agents/47-social-preview-tester.js.map +1 -0
- package/dist/agents/48-lighthouse-auditor.d.ts +11 -0
- package/dist/agents/48-lighthouse-auditor.d.ts.map +1 -0
- package/dist/agents/48-lighthouse-auditor.js +192 -0
- package/dist/agents/48-lighthouse-auditor.js.map +1 -0
- package/dist/agents/49-i18n-tester.d.ts +13 -0
- package/dist/agents/49-i18n-tester.d.ts.map +1 -0
- package/dist/agents/49-i18n-tester.js +172 -0
- package/dist/agents/49-i18n-tester.js.map +1 -0
- package/dist/agents/50-timezone-tester.d.ts +11 -0
- package/dist/agents/50-timezone-tester.d.ts.map +1 -0
- package/dist/agents/50-timezone-tester.js +152 -0
- package/dist/agents/50-timezone-tester.js.map +1 -0
- package/dist/agents/51-error-recovery-tester.d.ts +11 -0
- package/dist/agents/51-error-recovery-tester.d.ts.map +1 -0
- package/dist/agents/51-error-recovery-tester.js +134 -0
- package/dist/agents/51-error-recovery-tester.js.map +1 -0
- package/dist/agents/52-offline-mode-tester.d.ts +12 -0
- package/dist/agents/52-offline-mode-tester.d.ts.map +1 -0
- package/dist/agents/52-offline-mode-tester.js +161 -0
- package/dist/agents/52-offline-mode-tester.js.map +1 -0
- package/dist/agents/53-graceful-degradation-tester.d.ts +12 -0
- package/dist/agents/53-graceful-degradation-tester.d.ts.map +1 -0
- package/dist/agents/53-graceful-degradation-tester.js +130 -0
- package/dist/agents/53-graceful-degradation-tester.js.map +1 -0
- package/dist/agents/54-websocket-tester.d.ts +10 -0
- package/dist/agents/54-websocket-tester.d.ts.map +1 -0
- package/dist/agents/54-websocket-tester.js +132 -0
- package/dist/agents/54-websocket-tester.js.map +1 -0
- package/dist/agents/55-realtime-sync-tester.d.ts +11 -0
- package/dist/agents/55-realtime-sync-tester.d.ts.map +1 -0
- package/dist/agents/55-realtime-sync-tester.js +175 -0
- package/dist/agents/55-realtime-sync-tester.js.map +1 -0
- package/dist/agents/56-file-upload-tester.d.ts +12 -0
- package/dist/agents/56-file-upload-tester.d.ts.map +1 -0
- package/dist/agents/56-file-upload-tester.js +166 -0
- package/dist/agents/56-file-upload-tester.js.map +1 -0
- package/dist/agents/57-export-tester.d.ts +11 -0
- package/dist/agents/57-export-tester.d.ts.map +1 -0
- package/dist/agents/57-export-tester.js +155 -0
- package/dist/agents/57-export-tester.js.map +1 -0
- package/dist/agents/58-payment-flow-tester.d.ts +11 -0
- package/dist/agents/58-payment-flow-tester.d.ts.map +1 -0
- package/dist/agents/58-payment-flow-tester.js +159 -0
- package/dist/agents/58-payment-flow-tester.js.map +1 -0
- package/dist/agents/59-ssl-tls-auditor.d.ts +10 -0
- package/dist/agents/59-ssl-tls-auditor.d.ts.map +1 -0
- package/dist/agents/59-ssl-tls-auditor.js +132 -0
- package/dist/agents/59-ssl-tls-auditor.js.map +1 -0
- package/dist/agents/60-dns-cdn-tester.d.ts +10 -0
- package/dist/agents/60-dns-cdn-tester.d.ts.map +1 -0
- package/dist/agents/60-dns-cdn-tester.js +105 -0
- package/dist/agents/60-dns-cdn-tester.js.map +1 -0
- package/dist/agents/61-docker-health-checker.d.ts +10 -0
- package/dist/agents/61-docker-health-checker.d.ts.map +1 -0
- package/dist/agents/61-docker-health-checker.js +95 -0
- package/dist/agents/61-docker-health-checker.js.map +1 -0
- package/dist/agents/62-env-config-validator.d.ts +10 -0
- package/dist/agents/62-env-config-validator.d.ts.map +1 -0
- package/dist/agents/62-env-config-validator.js +132 -0
- package/dist/agents/62-env-config-validator.js.map +1 -0
- package/dist/agents/63-log-quality-auditor.d.ts +9 -0
- package/dist/agents/63-log-quality-auditor.d.ts.map +1 -0
- package/dist/agents/63-log-quality-auditor.js +121 -0
- package/dist/agents/63-log-quality-auditor.js.map +1 -0
- package/dist/agents/64-analytics-tracker-tester.d.ts +10 -0
- package/dist/agents/64-analytics-tracker-tester.d.ts.map +1 -0
- package/dist/agents/64-analytics-tracker-tester.js +146 -0
- package/dist/agents/64-analytics-tracker-tester.js.map +1 -0
- package/dist/agents/65-gdpr-compliance-tester.d.ts +13 -0
- package/dist/agents/65-gdpr-compliance-tester.d.ts.map +1 -0
- package/dist/agents/65-gdpr-compliance-tester.js +186 -0
- package/dist/agents/65-gdpr-compliance-tester.js.map +1 -0
- package/dist/agents/66-soc2-control-validator.d.ts +14 -0
- package/dist/agents/66-soc2-control-validator.d.ts.map +1 -0
- package/dist/agents/66-soc2-control-validator.js +178 -0
- package/dist/agents/66-soc2-control-validator.js.map +1 -0
- package/dist/agents/67-wcag-aaa-tester.d.ts +13 -0
- package/dist/agents/67-wcag-aaa-tester.d.ts.map +1 -0
- package/dist/agents/67-wcag-aaa-tester.js +207 -0
- package/dist/agents/67-wcag-aaa-tester.js.map +1 -0
- package/dist/agents/68-dead-code-detector.d.ts +9 -0
- package/dist/agents/68-dead-code-detector.d.ts.map +1 -0
- package/dist/agents/68-dead-code-detector.js +116 -0
- package/dist/agents/68-dead-code-detector.js.map +1 -0
- package/dist/agents/69-type-safety-auditor.d.ts +9 -0
- package/dist/agents/69-type-safety-auditor.d.ts.map +1 -0
- package/dist/agents/69-type-safety-auditor.js +148 -0
- package/dist/agents/69-type-safety-auditor.js.map +1 -0
- package/dist/agents/70-complexity-analyzer.d.ts +10 -0
- package/dist/agents/70-complexity-analyzer.d.ts.map +1 -0
- package/dist/agents/70-complexity-analyzer.js +154 -0
- package/dist/agents/70-complexity-analyzer.js.map +1 -0
- package/dist/agents/base-agent.d.ts +3 -1
- package/dist/agents/base-agent.d.ts.map +1 -1
- package/dist/agents/base-agent.js +7 -1
- package/dist/agents/base-agent.js.map +1 -1
- package/dist/agents/registry.d.ts +5 -4
- package/dist/agents/registry.d.ts.map +1 -1
- package/dist/agents/registry.js +170 -29
- package/dist/agents/registry.js.map +1 -1
- package/dist/clients/agent-mvp.d.ts +66 -0
- package/dist/clients/agent-mvp.d.ts.map +1 -0
- package/dist/clients/agent-mvp.js +91 -0
- package/dist/clients/agent-mvp.js.map +1 -0
- package/dist/clients/total-recall.d.ts +37 -0
- package/dist/clients/total-recall.d.ts.map +1 -0
- package/dist/clients/total-recall.js +224 -0
- package/dist/clients/total-recall.js.map +1 -0
- package/dist/core/cli.d.ts +3 -0
- package/dist/core/cli.d.ts.map +1 -1
- package/dist/core/cli.js +122 -6
- package/dist/core/cli.js.map +1 -1
- package/dist/core/license.js +2 -5
- package/dist/core/license.js.map +1 -1
- package/dist/core/orchestrator.d.ts +10 -1
- package/dist/core/orchestrator.d.ts.map +1 -1
- package/dist/core/orchestrator.js +66 -5
- package/dist/core/orchestrator.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import type { Finding } from '../core/types';
|
|
4
|
+
import { BaseAgent } from './base-agent';
|
|
5
|
+
import { resolveEnvironment } from '../helpers/env-resolver';
|
|
6
|
+
|
|
7
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
8
|
+
|
|
9
|
+
interface OpenApiPath {
|
|
10
|
+
[method: string]: {
|
|
11
|
+
responses?: Record<string, {
|
|
12
|
+
content?: Record<string, {
|
|
13
|
+
schema?: OpenApiSchema;
|
|
14
|
+
}>;
|
|
15
|
+
}>;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface OpenApiSchema {
|
|
20
|
+
type?: string;
|
|
21
|
+
properties?: Record<string, OpenApiSchema>;
|
|
22
|
+
items?: OpenApiSchema;
|
|
23
|
+
required?: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface OpenApiSpec {
|
|
27
|
+
paths?: Record<string, OpenApiPath>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class ApiContractTesterAgent extends BaseAgent {
|
|
31
|
+
readonly agentId = 39;
|
|
32
|
+
readonly agentName = 'API Contract Tester';
|
|
33
|
+
private baseUrl = '';
|
|
34
|
+
|
|
35
|
+
protected async preFlight(): Promise<void> {
|
|
36
|
+
const { env } = await resolveEnvironment(this.config, this.phase);
|
|
37
|
+
this.baseUrl = env.baseUrl;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
protected async execute(): Promise<Finding[]> {
|
|
41
|
+
const findings: Finding[] = [];
|
|
42
|
+
const projectRoot = this.config.projectRoot ?? process.cwd();
|
|
43
|
+
|
|
44
|
+
// Try to find OpenAPI spec
|
|
45
|
+
const specPaths = [
|
|
46
|
+
join(projectRoot, 'openapi.json'),
|
|
47
|
+
join(projectRoot, 'swagger.json'),
|
|
48
|
+
join(projectRoot, 'docs', 'openapi.json'),
|
|
49
|
+
join(projectRoot, 'docs', 'swagger.json'),
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
let spec: OpenApiSpec | null = null;
|
|
53
|
+
let specPath = '';
|
|
54
|
+
|
|
55
|
+
for (const path of specPaths) {
|
|
56
|
+
try {
|
|
57
|
+
const content = await readFile(path, 'utf-8');
|
|
58
|
+
spec = JSON.parse(content) as OpenApiSpec;
|
|
59
|
+
specPath = path;
|
|
60
|
+
break;
|
|
61
|
+
} catch {
|
|
62
|
+
// File not found — try next
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!spec) {
|
|
67
|
+
findings.push({
|
|
68
|
+
id: `${this.agentId}-no-spec`,
|
|
69
|
+
type: 'infra-issue',
|
|
70
|
+
severity: 'low',
|
|
71
|
+
agentId: this.agentId,
|
|
72
|
+
module: 'api-contract-tester',
|
|
73
|
+
description: 'No OpenAPI/Swagger spec found — cannot validate API contracts',
|
|
74
|
+
});
|
|
75
|
+
return findings;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.addEvidence({
|
|
79
|
+
type: 'report',
|
|
80
|
+
path: specPath,
|
|
81
|
+
description: `Using OpenAPI spec from ${specPath}`,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const paths = spec.paths ?? {};
|
|
85
|
+
|
|
86
|
+
for (const [path, methods] of Object.entries(paths)) {
|
|
87
|
+
for (const [method, definition] of Object.entries(methods)) {
|
|
88
|
+
if (method.startsWith('x-') || method === 'parameters') continue;
|
|
89
|
+
const httpMethod = method.toUpperCase();
|
|
90
|
+
if (!['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(httpMethod)) continue;
|
|
91
|
+
|
|
92
|
+
const contractFindings = await this.testEndpoint(path, httpMethod, definition);
|
|
93
|
+
findings.push(...contractFindings);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return findings;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private async testEndpoint(
|
|
101
|
+
path: string,
|
|
102
|
+
method: string,
|
|
103
|
+
definition: OpenApiPath[string],
|
|
104
|
+
): Promise<Finding[]> {
|
|
105
|
+
const findings: Finding[] = [];
|
|
106
|
+
const url = `${this.baseUrl}${path}`;
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const response = await Promise.race([
|
|
110
|
+
fetch(url, {
|
|
111
|
+
method,
|
|
112
|
+
headers: { 'Content-Type': 'application/json' },
|
|
113
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
114
|
+
}),
|
|
115
|
+
new Promise<never>((_, reject) =>
|
|
116
|
+
setTimeout(() => reject(new Error('Timeout')), REQUEST_TIMEOUT_MS),
|
|
117
|
+
),
|
|
118
|
+
]) as Response;
|
|
119
|
+
|
|
120
|
+
const statusStr = String(response.status);
|
|
121
|
+
const responseDef = definition.responses?.[statusStr] ?? definition.responses?.['200'];
|
|
122
|
+
|
|
123
|
+
if (!responseDef) {
|
|
124
|
+
return findings;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const jsonContent = responseDef.content?.['application/json'];
|
|
128
|
+
if (!jsonContent?.schema) {
|
|
129
|
+
return findings;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let body: unknown;
|
|
133
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
134
|
+
if (contentType.includes('application/json')) {
|
|
135
|
+
body = await response.json();
|
|
136
|
+
} else {
|
|
137
|
+
return findings;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const schemaFindings = this.validateSchema(
|
|
141
|
+
body,
|
|
142
|
+
jsonContent.schema,
|
|
143
|
+
`${method} ${path}`,
|
|
144
|
+
);
|
|
145
|
+
findings.push(...schemaFindings);
|
|
146
|
+
} catch {
|
|
147
|
+
// Request failed — endpoint may not be reachable
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return findings;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private validateSchema(
|
|
154
|
+
body: unknown,
|
|
155
|
+
schema: OpenApiSchema,
|
|
156
|
+
context: string,
|
|
157
|
+
): Finding[] {
|
|
158
|
+
const findings: Finding[] = [];
|
|
159
|
+
|
|
160
|
+
if (schema.type === 'object' && schema.properties && typeof body === 'object' && body !== null) {
|
|
161
|
+
const record = body as Record<string, unknown>;
|
|
162
|
+
|
|
163
|
+
// Check required fields
|
|
164
|
+
for (const field of schema.required ?? []) {
|
|
165
|
+
if (!(field in record)) {
|
|
166
|
+
findings.push({
|
|
167
|
+
id: `${this.agentId}-missing-field-${context.replace(/\s/g, '-')}-${field}`,
|
|
168
|
+
type: 'test-bug',
|
|
169
|
+
severity: 'medium',
|
|
170
|
+
agentId: this.agentId,
|
|
171
|
+
module: 'api-contract-tester',
|
|
172
|
+
description: `Missing required field "${field}" in response for ${context}`,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Check types of present fields
|
|
178
|
+
for (const [field, fieldSchema] of Object.entries(schema.properties)) {
|
|
179
|
+
if (field in record && fieldSchema.type) {
|
|
180
|
+
const actualType = Array.isArray(record[field]) ? 'array' : typeof record[field];
|
|
181
|
+
const expectedType = fieldSchema.type === 'integer' ? 'number' : fieldSchema.type;
|
|
182
|
+
|
|
183
|
+
if (actualType !== expectedType && record[field] !== null) {
|
|
184
|
+
findings.push({
|
|
185
|
+
id: `${this.agentId}-wrong-type-${context.replace(/\s/g, '-')}-${field}`,
|
|
186
|
+
type: 'test-bug',
|
|
187
|
+
severity: 'high',
|
|
188
|
+
agentId: this.agentId,
|
|
189
|
+
module: 'api-contract-tester',
|
|
190
|
+
description: `Wrong type for field "${field}" in ${context}: expected ${expectedType}, got ${actualType}`,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return findings;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Finding } from '../core/types';
|
|
2
|
+
import { BaseAgent } from './base-agent';
|
|
3
|
+
import { resolveEnvironment } from '../helpers/env-resolver';
|
|
4
|
+
|
|
5
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
6
|
+
const RAPID_REQUEST_COUNT = 50;
|
|
7
|
+
|
|
8
|
+
/** Default endpoints to test for rate limiting. */
|
|
9
|
+
const DEFAULT_ENDPOINTS = [
|
|
10
|
+
'/api/auth/login',
|
|
11
|
+
'/api/tasks',
|
|
12
|
+
'/api/clients',
|
|
13
|
+
] as const;
|
|
14
|
+
|
|
15
|
+
export class RateLimitTesterAgent extends BaseAgent {
|
|
16
|
+
readonly agentId = 40;
|
|
17
|
+
readonly agentName = 'Rate Limit Tester';
|
|
18
|
+
private baseUrl = '';
|
|
19
|
+
|
|
20
|
+
protected async preFlight(): Promise<void> {
|
|
21
|
+
const { env } = await resolveEnvironment(this.config, this.phase);
|
|
22
|
+
this.baseUrl = env.baseUrl;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
protected async execute(): Promise<Finding[]> {
|
|
26
|
+
const findings: Finding[] = [];
|
|
27
|
+
const endpoints = this.discoverEndpoints();
|
|
28
|
+
|
|
29
|
+
for (const endpoint of endpoints) {
|
|
30
|
+
const endpointFindings = await this.testRateLimit(endpoint);
|
|
31
|
+
findings.push(...endpointFindings);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return findings;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private discoverEndpoints(): string[] {
|
|
38
|
+
if (this.config.modules && this.config.modules.length > 0) {
|
|
39
|
+
return this.config.modules.map(m => `/api${m.route}`);
|
|
40
|
+
}
|
|
41
|
+
return [...DEFAULT_ENDPOINTS];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private async testRateLimit(endpoint: string): Promise<Finding[]> {
|
|
45
|
+
const findings: Finding[] = [];
|
|
46
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
47
|
+
let got429 = false;
|
|
48
|
+
let successCount = 0;
|
|
49
|
+
let errorCount = 0;
|
|
50
|
+
|
|
51
|
+
const promises = Array.from({ length: RAPID_REQUEST_COUNT }, async () => {
|
|
52
|
+
try {
|
|
53
|
+
const response = await Promise.race([
|
|
54
|
+
fetch(url, {
|
|
55
|
+
method: 'GET',
|
|
56
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
57
|
+
}),
|
|
58
|
+
new Promise<never>((_, reject) =>
|
|
59
|
+
setTimeout(() => reject(new Error('Timeout')), REQUEST_TIMEOUT_MS),
|
|
60
|
+
),
|
|
61
|
+
]) as Response;
|
|
62
|
+
|
|
63
|
+
if (response.status === 429) {
|
|
64
|
+
got429 = true;
|
|
65
|
+
} else {
|
|
66
|
+
successCount++;
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
errorCount++;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await Promise.all(promises);
|
|
74
|
+
|
|
75
|
+
if (!got429) {
|
|
76
|
+
findings.push({
|
|
77
|
+
id: `${this.agentId}-no-rate-limit-${endpoint.replace(/\//g, '_')}`,
|
|
78
|
+
type: 'code-bug-security',
|
|
79
|
+
severity: 'medium',
|
|
80
|
+
agentId: this.agentId,
|
|
81
|
+
module: 'rate-limit-tester',
|
|
82
|
+
description: `No rate limiting on endpoint ${endpoint} — sent ${RAPID_REQUEST_COUNT} rapid requests with no 429 response`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.addEvidence({
|
|
87
|
+
type: 'log',
|
|
88
|
+
path: `rate-limit-${endpoint.replace(/\//g, '_')}`,
|
|
89
|
+
description: `Rate limit test for ${endpoint}: 429=${got429}, successes=${successCount}, errors=${errorCount}`,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return findings;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { Finding } from '../core/types';
|
|
2
|
+
import { BaseAgent } from './base-agent';
|
|
3
|
+
import { resolveEnvironment } from '../helpers/env-resolver';
|
|
4
|
+
|
|
5
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
6
|
+
|
|
7
|
+
/** Edge-case pagination parameters to test. */
|
|
8
|
+
const PAGINATION_TESTS = [
|
|
9
|
+
{ param: 'page=0', description: 'page=0 (zero-indexed edge case)' },
|
|
10
|
+
{ param: 'page=-1', description: 'page=-1 (negative page)' },
|
|
11
|
+
{ param: 'page=99999', description: 'page=99999 (very high page)' },
|
|
12
|
+
{ param: 'limit=0', description: 'limit=0 (zero limit)' },
|
|
13
|
+
{ param: 'limit=10000', description: 'limit=10000 (excessively high limit)' },
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
export class ApiPaginationTesterAgent extends BaseAgent {
|
|
17
|
+
readonly agentId = 41;
|
|
18
|
+
readonly agentName = 'API Pagination Tester';
|
|
19
|
+
private baseUrl = '';
|
|
20
|
+
|
|
21
|
+
protected async preFlight(): Promise<void> {
|
|
22
|
+
const { env } = await resolveEnvironment(this.config, this.phase);
|
|
23
|
+
this.baseUrl = env.baseUrl;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
protected async execute(): Promise<Finding[]> {
|
|
27
|
+
const findings: Finding[] = [];
|
|
28
|
+
const endpoints = this.discoverListEndpoints();
|
|
29
|
+
|
|
30
|
+
for (const endpoint of endpoints) {
|
|
31
|
+
for (const test of PAGINATION_TESTS) {
|
|
32
|
+
const testFindings = await this.testPagination(endpoint, test.param, test.description);
|
|
33
|
+
findings.push(...testFindings);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return findings;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private discoverListEndpoints(): string[] {
|
|
41
|
+
if (this.config.modules && this.config.modules.length > 0) {
|
|
42
|
+
return this.config.modules.map(m => `/api${m.route}`);
|
|
43
|
+
}
|
|
44
|
+
return ['/api/tasks', '/api/clients'];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private async testPagination(
|
|
48
|
+
endpoint: string,
|
|
49
|
+
queryParam: string,
|
|
50
|
+
description: string,
|
|
51
|
+
): Promise<Finding[]> {
|
|
52
|
+
const findings: Finding[] = [];
|
|
53
|
+
const url = `${this.baseUrl}${endpoint}?${queryParam}`;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const response = await Promise.race([
|
|
57
|
+
fetch(url, {
|
|
58
|
+
method: 'GET',
|
|
59
|
+
headers: { 'Content-Type': 'application/json' },
|
|
60
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
61
|
+
}),
|
|
62
|
+
new Promise<never>((_, reject) =>
|
|
63
|
+
setTimeout(() => reject(new Error('Timeout')), REQUEST_TIMEOUT_MS),
|
|
64
|
+
),
|
|
65
|
+
]) as Response;
|
|
66
|
+
|
|
67
|
+
// Check for server errors (crashes)
|
|
68
|
+
if (response.status >= 500) {
|
|
69
|
+
findings.push({
|
|
70
|
+
id: `${this.agentId}-crash-${endpoint.replace(/\//g, '_')}-${queryParam.replace(/[=&]/g, '_')}`,
|
|
71
|
+
type: 'code-bug-logic',
|
|
72
|
+
severity: 'high',
|
|
73
|
+
agentId: this.agentId,
|
|
74
|
+
module: 'api-pagination-tester',
|
|
75
|
+
description: `Endpoint ${endpoint} returned ${response.status} for ${description} — server crash on edge-case pagination`,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.addEvidence({
|
|
80
|
+
type: 'log',
|
|
81
|
+
path: `pagination-${endpoint.replace(/\//g, '_')}-${queryParam.replace(/[=&]/g, '_')}`,
|
|
82
|
+
description: `${endpoint}?${queryParam}: status=${response.status}`,
|
|
83
|
+
});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
findings.push({
|
|
86
|
+
id: `${this.agentId}-error-${endpoint.replace(/\//g, '_')}-${queryParam.replace(/[=&]/g, '_')}`,
|
|
87
|
+
type: 'infra-issue',
|
|
88
|
+
severity: 'low',
|
|
89
|
+
agentId: this.agentId,
|
|
90
|
+
module: 'api-pagination-tester',
|
|
91
|
+
description: `Failed to test ${endpoint} with ${description}: ${error instanceof Error ? error.message : String(error)}`,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return findings;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import type { Finding } from '../core/types';
|
|
2
|
+
import { BaseAgent } from './base-agent';
|
|
3
|
+
import { resolveEnvironment } from '../helpers/env-resolver';
|
|
4
|
+
|
|
5
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
6
|
+
|
|
7
|
+
const GRAPHQL_PATHS = ['/graphql', '/api/graphql'] as const;
|
|
8
|
+
|
|
9
|
+
/** Standard introspection query. */
|
|
10
|
+
const INTROSPECTION_QUERY = '{ __schema { types { name } } }';
|
|
11
|
+
|
|
12
|
+
/** Deeply nested query (10+ levels). */
|
|
13
|
+
function buildDeepQuery(depth: number): string {
|
|
14
|
+
let query = '{ __typename';
|
|
15
|
+
const openBraces: string[] = [];
|
|
16
|
+
for (let i = 0; i < depth; i++) {
|
|
17
|
+
query += ` __type(name: "Query") { name fields { name type { name`;
|
|
18
|
+
openBraces.push(' } }');
|
|
19
|
+
}
|
|
20
|
+
query += openBraces.reverse().join('');
|
|
21
|
+
query += ' }';
|
|
22
|
+
return query;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class GraphqlTesterAgent extends BaseAgent {
|
|
26
|
+
readonly agentId = 42;
|
|
27
|
+
readonly agentName = 'GraphQL Tester';
|
|
28
|
+
private baseUrl = '';
|
|
29
|
+
|
|
30
|
+
protected async preFlight(): Promise<void> {
|
|
31
|
+
const { env } = await resolveEnvironment(this.config, this.phase);
|
|
32
|
+
this.baseUrl = env.baseUrl;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
protected async execute(): Promise<Finding[]> {
|
|
36
|
+
const findings: Finding[] = [];
|
|
37
|
+
|
|
38
|
+
// Discover GraphQL endpoint
|
|
39
|
+
let graphqlUrl: string | null = null;
|
|
40
|
+
|
|
41
|
+
for (const path of GRAPHQL_PATHS) {
|
|
42
|
+
const url = `${this.baseUrl}${path}`;
|
|
43
|
+
try {
|
|
44
|
+
const response = await Promise.race([
|
|
45
|
+
fetch(url, {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: { 'Content-Type': 'application/json' },
|
|
48
|
+
body: JSON.stringify({ query: '{ __typename }' }),
|
|
49
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
50
|
+
}),
|
|
51
|
+
new Promise<never>((_, reject) =>
|
|
52
|
+
setTimeout(() => reject(new Error('Timeout')), REQUEST_TIMEOUT_MS),
|
|
53
|
+
),
|
|
54
|
+
]) as Response;
|
|
55
|
+
|
|
56
|
+
if (response.status !== 404) {
|
|
57
|
+
graphqlUrl = url;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
// Path not available
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!graphqlUrl) {
|
|
66
|
+
findings.push({
|
|
67
|
+
id: `${this.agentId}-no-graphql`,
|
|
68
|
+
type: 'infra-issue',
|
|
69
|
+
severity: 'low',
|
|
70
|
+
agentId: this.agentId,
|
|
71
|
+
module: 'graphql-tester',
|
|
72
|
+
description: 'No GraphQL endpoint found at /graphql or /api/graphql',
|
|
73
|
+
});
|
|
74
|
+
return findings;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Test 1: Introspection
|
|
78
|
+
const introspectionFindings = await this.testIntrospection(graphqlUrl);
|
|
79
|
+
findings.push(...introspectionFindings);
|
|
80
|
+
|
|
81
|
+
// Test 2: Deep query (depth limiting)
|
|
82
|
+
const depthFindings = await this.testQueryDepth(graphqlUrl);
|
|
83
|
+
findings.push(...depthFindings);
|
|
84
|
+
|
|
85
|
+
// Test 3: Unauthenticated access
|
|
86
|
+
const authFindings = await this.testUnauthenticatedAccess(graphqlUrl);
|
|
87
|
+
findings.push(...authFindings);
|
|
88
|
+
|
|
89
|
+
return findings;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private async testIntrospection(graphqlUrl: string): Promise<Finding[]> {
|
|
93
|
+
const findings: Finding[] = [];
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const response = await Promise.race([
|
|
97
|
+
fetch(graphqlUrl, {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers: { 'Content-Type': 'application/json' },
|
|
100
|
+
body: JSON.stringify({ query: INTROSPECTION_QUERY }),
|
|
101
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
102
|
+
}),
|
|
103
|
+
new Promise<never>((_, reject) =>
|
|
104
|
+
setTimeout(() => reject(new Error('Timeout')), REQUEST_TIMEOUT_MS),
|
|
105
|
+
),
|
|
106
|
+
]) as Response;
|
|
107
|
+
|
|
108
|
+
if (response.status === 200) {
|
|
109
|
+
const body = await response.json() as { data?: { __schema?: unknown } };
|
|
110
|
+
if (body.data?.__schema) {
|
|
111
|
+
findings.push({
|
|
112
|
+
id: `${this.agentId}-introspection-enabled`,
|
|
113
|
+
type: 'code-bug-security',
|
|
114
|
+
severity: 'medium',
|
|
115
|
+
agentId: this.agentId,
|
|
116
|
+
module: 'graphql-tester',
|
|
117
|
+
description: 'GraphQL introspection is enabled in production — schema is exposed to attackers',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.addEvidence({
|
|
123
|
+
type: 'log',
|
|
124
|
+
path: 'graphql-introspection',
|
|
125
|
+
description: `Introspection query: status=${response.status}`,
|
|
126
|
+
});
|
|
127
|
+
} catch {
|
|
128
|
+
// Request failed
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return findings;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private async testQueryDepth(graphqlUrl: string): Promise<Finding[]> {
|
|
135
|
+
const findings: Finding[] = [];
|
|
136
|
+
const deepQuery = buildDeepQuery(10);
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const response = await Promise.race([
|
|
140
|
+
fetch(graphqlUrl, {
|
|
141
|
+
method: 'POST',
|
|
142
|
+
headers: { 'Content-Type': 'application/json' },
|
|
143
|
+
body: JSON.stringify({ query: deepQuery }),
|
|
144
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
145
|
+
}),
|
|
146
|
+
new Promise<never>((_, reject) =>
|
|
147
|
+
setTimeout(() => reject(new Error('Timeout')), REQUEST_TIMEOUT_MS),
|
|
148
|
+
),
|
|
149
|
+
]) as Response;
|
|
150
|
+
|
|
151
|
+
if (response.status === 200) {
|
|
152
|
+
const body = await response.json() as { errors?: unknown[] };
|
|
153
|
+
if (!body.errors) {
|
|
154
|
+
findings.push({
|
|
155
|
+
id: `${this.agentId}-no-depth-limit`,
|
|
156
|
+
type: 'code-bug-security',
|
|
157
|
+
severity: 'high',
|
|
158
|
+
agentId: this.agentId,
|
|
159
|
+
module: 'graphql-tester',
|
|
160
|
+
description: 'No query depth limit on GraphQL endpoint — deeply nested queries (10+ levels) are accepted without error',
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
this.addEvidence({
|
|
166
|
+
type: 'log',
|
|
167
|
+
path: 'graphql-depth',
|
|
168
|
+
description: `Deep query test (10 levels): status=${response.status}`,
|
|
169
|
+
});
|
|
170
|
+
} catch {
|
|
171
|
+
// Request failed
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return findings;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private async testUnauthenticatedAccess(graphqlUrl: string): Promise<Finding[]> {
|
|
178
|
+
const findings: Finding[] = [];
|
|
179
|
+
|
|
180
|
+
// Send a query that would typically need auth
|
|
181
|
+
const query = '{ users { id email } }';
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const response = await Promise.race([
|
|
185
|
+
fetch(graphqlUrl, {
|
|
186
|
+
method: 'POST',
|
|
187
|
+
headers: { 'Content-Type': 'application/json' },
|
|
188
|
+
// No Authorization header
|
|
189
|
+
body: JSON.stringify({ query }),
|
|
190
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
191
|
+
}),
|
|
192
|
+
new Promise<never>((_, reject) =>
|
|
193
|
+
setTimeout(() => reject(new Error('Timeout')), REQUEST_TIMEOUT_MS),
|
|
194
|
+
),
|
|
195
|
+
]) as Response;
|
|
196
|
+
|
|
197
|
+
if (response.status === 200) {
|
|
198
|
+
const body = await response.json() as { data?: { users?: unknown[] } };
|
|
199
|
+
if (body.data?.users && Array.isArray(body.data.users) && body.data.users.length > 0) {
|
|
200
|
+
findings.push({
|
|
201
|
+
id: `${this.agentId}-no-field-auth`,
|
|
202
|
+
type: 'code-bug-security',
|
|
203
|
+
severity: 'high',
|
|
204
|
+
agentId: this.agentId,
|
|
205
|
+
module: 'graphql-tester',
|
|
206
|
+
description: 'GraphQL returns user data without authentication — field-level authorization is missing',
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.addEvidence({
|
|
212
|
+
type: 'log',
|
|
213
|
+
path: 'graphql-unauth',
|
|
214
|
+
description: `Unauthenticated query test: status=${response.status}`,
|
|
215
|
+
});
|
|
216
|
+
} catch {
|
|
217
|
+
// Request failed
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return findings;
|
|
221
|
+
}
|
|
222
|
+
}
|