Skip to content

Broken task: detection and status, server side - #2509

Open
thatux wants to merge 8 commits into
hashtopolis:masterfrom
thatux:broken-task-detection
Open

thatux wants to merge 8 commits into
hashtopolis:masterfrom
thatux:broken-task-detection

Conversation

@thatux

@thatux thatux commented Sep 25, 2026

Copy link
Copy Markdown

fixes #884

A bad task command makes every agent that picks it up fail. The old behaviour
deactivated them one by one until the fleet was offline. This marks the task
broken instead and exposes it to the UI. Detection works with the stock agent
(it already reports failures); the companion #746 makes the reported error
useful instead of a bare "Keyspace measure failed!".

Reproduction

Give a task an invalid mask so its keyspace measure fails, register one agent,
let it poll:

before: the agent is reassigned the task and loops on it forever
after:  BrokenTask row, task excluded from getTask, agent stays active

Changes

  • [EPIC] Agent error handeling and logging #884 (AgentErrorUtils): brokenTaskThreshold distinct agents failing
    marks the task broken (not the agents); an agent failing across
    brokenAgentThreshold tasks is deactivated instead. Broken tasks are skipped
    in getCandidateTasks. Thresholds configurable.
  • Threshold capped at the active-agent count (min(configured, active)),
    recomputed per error, so a single agent faults immediately instead of looping.
  • A task any agent is progressing on (a chunk with non-zero progress) is never
    faulted.
  • Editing the attack command clears the broken flag.
  • isBroken aggregate on task and taskwrapperdisplay, brokenReason on
    task (the causing agent error). BrokenTaskAPI (GET/DELETE) already exists.

Testing

vendor/bin/phpunit --filter AgentErrorUtils   # OK, 5 tests, 83 assertions

A bad task (a wrong hashcat command, a syntax error) used to deactivate
every agent that picked it up, one by one, until the whole fleet went
offline. Nodes are untrusted (a cracking contest), so a single node
failing is a driver or hardware problem on that node, not proof the task
is broken.

On a client error the agent is no longer deactivated on its first error.
AgentErrorUtils now decides between the two failure modes:
 - if brokenTaskThreshold DISTINCT agents have failed on one task, the
   task is marked broken (recorded in the new BrokenTask table) and is not
   handed out again until an operator clears it; the reporting agents stay
   active;
 - if an agent has failed on brokenAgentThreshold distinct tasks, the
   agent itself is deactivated.

An apiv2 endpoint (/api/v2/ui/brokentasks, GET + DELETE) lists broken
tasks and clears one, which is a manual retry after the operator has
looked at it.

Three thresholds are configurable (Cracking/Tasks section):
 - brokenTaskThreshold  (default 2, distinct agents; 0 disables)
 - brokenAgentThreshold (default 3, distinct tasks; 0 disables)
 - brokenErrorWindow    (default 3600s; 0 counts the whole history)

Setting brokenTaskThreshold to 0 and brokenAgentThreshold to 1 reproduces
the previous deactivate-on-first-error behaviour.

Implements the server side of the hashtopolis#884 epic (and the hashtopolis#2487 duplicate).
Add an isBroken aggregate to the task and taskwrapperdisplay resources, and a
brokenReason aggregate to the task resource. The task list (taskwrapperdisplays)
flags broken tasks and the task edit page shows why, without a separate request
or a dedicated page.
Marking a task broken counted only the agents that failed. If two nodes share a
driver fault while a third node is cracking the same task fine, the working node
proves the command runs, so the failures are agent faults, not a task fault.
Skip markTaskBroken when any chunk on the task has non-zero progress; a genuinely
broken command never progresses, so real detection is unaffected.
With fewer active agents than the configured threshold a task could never reach
it, so an agent would loop on a broken task forever. Cap the threshold at the
number of active agents (recomputed per error): a single-agent setup faults a
task on the first distinct failure, while a larger fleet keeps the full
threshold. Adds a test for the single-active-agent case.
… on edit

brokenReason now returns the actual agent error that broke the task (its most
recent agent error) rather than the generic auto-marked text, so the UI explains
what went wrong. Editing the attack command clears the task's broken flag, since
that is an admin fixing it; if it is still bad the agents mark it broken again.
@thatux thatux changed the title Broken task detection and status, server side Broken task: detection and status, server side Sep 25, 2026
…the mark-broken race

Review fixes for the broken-task detection:

- Cap the effective threshold at the number of agents ELIGIBLE for the task
  (same access group, trusted for any secret hashlist or file), not the whole
  active fleet. A task reachable by only a subset smaller than the configured
  threshold no longer loops forever on its eligible agents.
- markTaskBroken swallows a UNIQUE(taskId) violation from a concurrent error on
  the same task instead of surfacing a 500 to the reporting agent.
- Rely on changeAttackCmd -> purgeTask to clear the BrokenTask row when the
  command is edited; drop the redundant clearBrokenTask call, so resubmitting
  the same command no longer clears a broken task.
- Note in a comment that a task with any chunk progress is never auto-faulted.

Tests put agents in the task's access group so they count as eligible, and a new
test asserts the threshold caps at eligible agents rather than the active fleet.
@thatux

thatux commented Sep 26, 2026 •

Copy link
Copy Markdown
Author

Changing the attackcmd removes the 'broken' mark from the task, or you can use the 'mark not broken button'.

signal-2026-09-26-12-14-18-282 signal-2026-09-26-12-14-18-282-2 signal-2026-09-26-13-20-56-355

The broken-task work added an API (BrokenTaskAPI) and the isBroken/brokenReason
aggregates but did not regenerate the artifacts CI checks against:

- openapi.json regenerated (ci/tools/generate-openapi.php) so the committed
  schema matches the one the server serves (openapi-lint).
- BrokenTask model and factory regenerated with generator.php so the generated
  DBA sources match (dba-generator); the diff was whitespace only.
- Mark isTaskBroken() @phpstan-impure: it reads the database, so the second
  check in markTaskBroken's race guard is not dead code.
mockConfig replaced the whole SConfig singleton with a DataSet holding only the
three broken-task thresholds, so every other config lookup returned null for the
duration of the test. In particular Util::createLogEntry (reached via
markTaskBroken -> DServerLog::log) reads numLogEntries for its log rotation; with
it null the rotation deleted the oldest LogEntry rows, including id 1. That broke
the unrelated apiv2 test_logentry (LogEntry.get(pk=1)) later in the same CI run
on the synthetic dataset.

Build the mock from the real config (SConfig::getInstance()->getAllValues()) and
override only the three thresholds, so unrelated config keeps its real values.
@thatux

thatux commented Sep 26, 2026

Copy link
Copy Markdown
Author

CI should work now

@jessevz

jessevz commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

I really like the feature, but I am not really sure if checking whether a task is broken by a n amount of agents is the way to go. Preferably I would like it if hashcat could return a status code for wrong user input. If I read https://github.com/hashcat/hashcat/blob/master/docs/exit_status_code.txt I think it could be -1. So if we could validate and use that, that could be perfect. Another solution could be to parse the exact error message hashcat throws on - - keyspace. If the error is "error during keyspace calculation", I think we can fairly surely say it was an user input error in the task.

@novasam23

Copy link
Copy Markdown
Contributor

Agreed, this feature is really nice to have and heavily requested.

I also would propose to change the implementation, though. @jessevz already elaborated on the "trigger logic" and how to implement that.

As for the implementation regarding the application model, I'd suggest to introduce an additional task status DTaskStatus BROKEN instead of a new model to represent a broken task. The auxiliary fields needed to store additional information for a broken task can be achieved by adding columns to the task schema. That should maybe make for a sleeker implementation overall.

It'd be appreciated if you find time to explore these alternative implementations.

@s3inlc

s3inlc commented Oct 2, 2026

Copy link
Copy Markdown
Member

Maybe it makes sense to wait a moment before implementing further too much. Currently we have the stack open which brings background jobs and local downloads of hashcat to the server. This would then allow calculating --keyspace in the backend with the corresponding cracker binary. With this calculation any broken task should typically surface as well (as keyspace is where it normally already fails, not later).

Or are there other use cases, where a task would be considered broken but does work normally on the keyspace calculation?

@thatux

thatux commented Oct 2, 2026

Copy link
Copy Markdown
Author

The reason for testing with multiple agents instead of feedback from the cracker is twofold:

  1. All crackers would need to support checking whether a task 'works' that could be an --verify-command flag or checking statuscode. The current implementation doesn't require that (it's only hashtopolis code that needs to be changed).
  2. AFAIK hashtopolis design regards nodes as untrusted, therefore if a node doesn't work that is because of e.g. the drivers and not due to an incorrect command. So we want to make sure that it's not a single node that rejects this, but multiple.

additional task status DTaskStatus BROKEN instead of a new model to represent a broken task.
A supertask can be both broken and running, hence the my choice of not using DTaskStatus for this.

@s3inlc can you link me to the background task implementation (out of interest)

@s3inlc

s3inlc commented Oct 16, 2026

Copy link
Copy Markdown
Member

The background job lives currently here: #2502

For the moment let's wait with actual development on this feature and wait for the stacked PRs to be merged and then do it in a heuristic way that on an error either the task gets marked as broken or the agent gets deactivated. We can then see if we continue on this branch or use it as a basis for a new one.

What would help to already have is a conceptual heuristic, how (based on the exit code of hashcat and potential error messages to stderr) to determine if an error is likely the reason to a faulty task or a faulty agent.

@thatux

thatux commented Oct 24, 2026

Copy link
Copy Markdown
Author

Hashcat master now has dedicated exit codes that help distinguish these cases: command/task errors and bridge/plugin errors.

I'd suggest this routing:

Exit code Meaning Suggested handling
-2 / 254 Invalid attack command, including a missing path Mark the task broken if server-side validation reproduces the command error. For missing paths on an agent, check file synchronization first.
-12 / 244 An input file was read but its contents were unusable Verify the agent has the expected file. If the server's copy is also unusable, mark the task broken.
-13 / 243 Bridge or plugin failed Inspect stderr. Missing dependencies can be agent-local, while a defective plugin can affect every agent.
-3 through -11, excluding reserved -7 Backend, memory, compilation, autotune or self-test failure Initially stop assigning this task to that agent/device. One failure should not globally deactivate the agent.
-1 / 255 Unclassified or combined errors Retain stderr and retry or corroborate before assigning blame. Permission and other I/O failures can still land here.

The paired positive numbers are POSIX shell exit statuses. Cracked/exhausted (0/1) and intentional stops (2 through 5) should not trigger broken-task detection.

The exit code would be the primary classifier, with stderr providing the reason and resolving ambiguous cases. Server-side checks with the matching binary avoid trusting a single agent's report, although a successful --keyspace alone does not validate input-file contents or execution on an agent.

For an MVP, I'd require a hashcat build containing these changes and leave older binaries on the existing handling. That avoids adding stderr-pattern classification for older versions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[EPIC] Agent error handeling and logging

4 participants