Summary
Scanner.scan() bounds the overall scan with CompletableFuture.allOf(futures...).orTimeout(config.getTimeoutMinutes(), TimeUnit.MINUTES).exceptionally(...), which should cause the scan to give up and return whatever findings it has after the configured timeout (default 30 minutes). In practice, a scan was observed to stall completely and never exit — no further log output, no timeout warning logged, no report written — for hours past the configured limit, until the JVM process was force-killed manually.
Location
src/main/java/org/owasp/astf/core/Scanner.java:111-159
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
...
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.orTimeout(config.getTimeoutMinutes(), TimeUnit.MINUTES)
.exceptionally(ex -> {
logger.warn("Scan interrupted or timed out before completion: {}", ex.getMessage());
return null;
})
.join();
} // <-- try-with-resources calls executor.close(), which blocks awaiting
// termination of any still-running virtual threads
ExecutorService.close() (the try-with-resources exit path, Java 19+) calls shutdown() and then blocks awaiting termination of in-flight tasks, escalating to shutdownNow()/interrupt only after further waiting. If one or more of the per-endpoint/per-test-case tasks is stuck in a way that doesn't respond to interruption, close() can block indefinitely — silently defeating the orTimeout that fired (or was supposed to fire) just before it.
Reproduction
Ran ASTF v1.0.0 against a local OWASP crAPI instance with a valid auth token (--token, 44 endpoints × 12 test cases = 528 tasks). Progress logging reached:
Scan progress: 96.6% (510/528 tasks completed)
...and then produced zero further output — no more progress lines, no per-task error logs, no "Scan interrupted or timed out" warning, no completed report — for several hours of wall-clock time, well past the default 30-minute --timeout. The process had to be force-killed (taskkill /F) to recover. Console log (602 lines) contains no WARN/timed out/interrupted entries at all, suggesting the orTimeout branch was never reached or its effect was masked by a subsequent hang in executor.close().
The exact stuck task (endpoint + test case) wasn't isolated in this run, but the failure mode itself — no timeout enforcement, no error, no exit — is the concerning part for CI/unattended use.
Suggested fix
- Don't rely on try-with-resources
close() to bound shutdown time. After the orTimeout/exceptionally stage completes (by timeout or normally), explicitly call executor.shutdownNow() and awaitTermination() with its own short bounded wait, then proceed to write the report regardless of whether all virtual threads have fully terminated.
- Ensure whatever findings were collected up to the timeout are still written to the report (partial results are much more useful than no output).
- Consider a per-task timeout in addition to the whole-scan timeout, so a single stuck request/test case can't stall the batch.
Impact
A scan that hangs past its configured timeout with no report and no error is a reliability risk for any automated/CI usage of ASTF — the process has to be babysat and force-killed rather than trusted to exit on its own.
Companion to #70, #71, #72, #73 — found during a manual verification pass of ASTF v1.0.0 against VAmPI, crAPI, DVGA, and gRPC Goat.
Summary
Scanner.scan()bounds the overall scan withCompletableFuture.allOf(futures...).orTimeout(config.getTimeoutMinutes(), TimeUnit.MINUTES).exceptionally(...), which should cause the scan to give up and return whatever findings it has after the configured timeout (default 30 minutes). In practice, a scan was observed to stall completely and never exit — no further log output, no timeout warning logged, no report written — for hours past the configured limit, until the JVM process was force-killed manually.Location
src/main/java/org/owasp/astf/core/Scanner.java:111-159ExecutorService.close()(the try-with-resources exit path, Java 19+) callsshutdown()and then blocks awaiting termination of in-flight tasks, escalating toshutdownNow()/interrupt only after further waiting. If one or more of the per-endpoint/per-test-case tasks is stuck in a way that doesn't respond to interruption,close()can block indefinitely — silently defeating theorTimeoutthat fired (or was supposed to fire) just before it.Reproduction
Ran ASTF v1.0.0 against a local OWASP crAPI instance with a valid auth token (
--token, 44 endpoints × 12 test cases = 528 tasks). Progress logging reached:...and then produced zero further output — no more progress lines, no per-task error logs, no
"Scan interrupted or timed out"warning, no completed report — for several hours of wall-clock time, well past the default 30-minute--timeout. The process had to be force-killed (taskkill /F) to recover. Console log (602 lines) contains noWARN/timed out/interruptedentries at all, suggesting theorTimeoutbranch was never reached or its effect was masked by a subsequent hang inexecutor.close().The exact stuck task (endpoint + test case) wasn't isolated in this run, but the failure mode itself — no timeout enforcement, no error, no exit — is the concerning part for CI/unattended use.
Suggested fix
close()to bound shutdown time. After theorTimeout/exceptionallystage completes (by timeout or normally), explicitly callexecutor.shutdownNow()andawaitTermination()with its own short bounded wait, then proceed to write the report regardless of whether all virtual threads have fully terminated.Impact
A scan that hangs past its configured timeout with no report and no error is a reliability risk for any automated/CI usage of ASTF — the process has to be babysat and force-killed rather than trusted to exit on its own.
Companion to #70, #71, #72, #73 — found during a manual verification pass of ASTF v1.0.0 against VAmPI, crAPI, DVGA, and gRPC Goat.