Live verification scoring + standalone score command - #355
Live verification scoring + standalone score command#355mkultraWasHere wants to merge 38 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a standalone dreadgoad score command that can optionally live-verify agent-reported credentials against a running GOAD lab (via nxc smb + secretsdump.py executed on the Kali attack box). It also removes the prior inference/technique-scoring approach, keeping the scoreboard TUI fast by remaining static-only.
Changes:
- Adds
dreadgoad score+score generate-key, with shared scoring logic (ScoreReport) and optional live verification (LiveVerifier). - Removes inference-driven host/domain/technique credit; static mode now only scores credentials, while live mode verifies hosts/domains directly.
- Adds AWS SSM + Azure Bastion SSH command runners to execute verification tooling on the Kali attack box; updates docs and agent reporting prompt accordingly.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| scoreboard/agent_prompt.md | Updates agent reporting spec (header + ./report.jsonl, host findings require hostname, scoring guidance). |
| docs/scoring.md | New standalone scoring documentation including live verification workflow and JSON output format. |
| docs/scoreboard.md | Updates scoreboard docs to point to score generate-key and clarifies static-only scoreboard behavior. |
| docs/design-live-verification.md | Adds design doc for live verification architecture and scoring model. |
| cli/internal/scoreboard/verify.go | Strips inference/technique logic; keeps static credential verification for the TUI. |
| cli/internal/scoreboard/verify_test.go | Updates tests to reflect static-only verification (credentials only) + new verify types. |
| cli/internal/scoreboard/types.go | Extends objective/output types (host_ip/dc_ip/netbios, JSON tags, ScoreResult, FailedCheck). |
| cli/internal/scoreboard/tui.go | Removes technique group and adds a static-only warning banner; adjusts layout sizing. |
| cli/internal/scoreboard/transport_ares.go | Simplifies Ares report synthesis (drops exploited-technique emission; keeps domain signals). |
| cli/internal/scoreboard/shell_ssm.go | Adds an SSM-based shell runner for executing commands on Kali (AWS-RunShellScript). |
| cli/internal/scoreboard/shell_bastion.go | Adds an Azure Bastion SSH-based shell runner for executing commands on Kali via az network bastion ssh. |
| cli/internal/scoreboard/score.go | Adds shared scoring routine (ScoreReport) including host/domain live verification phases. |
| cli/internal/scoreboard/live.go | Implements ShellRunner + LiveVerifier with nxc/secretsdump command building, parsing, and caching. |
| cli/internal/scoreboard/generate.go | Removes technique objective generation; adds ACL-based live_auth classification and new verify types/NetBIOS. |
| cli/internal/azure/kali.go | Exports azure.KaliKeyPath() and adds Azure discovery of the Kali VM by tag. |
| cli/cmd/scoreboard.go | Removes generate-key command, keeping a hidden alias pointing to score generate-key. |
| cli/cmd/score.go | Adds dreadgoad score command (summary + JSON output) and wiring for AWS/Azure live verification. |
| cli/cmd/bastion.go | Switches to the exported azure.KaliKeyPath() helper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 31 out of 32 changed files in this pull request and generated 3 comments.
Files not reviewed (1)
- modules/terraform-azure-kali/.terraform.lock.hcl: Generated file
Comments suppressed due to low confidence (1)
scoreboard/agent_prompt-2.md:59
- This section says findings are scored on "passwords obtained", but the format and examples allow
evidenceto be an NTLM hash as well. Updating the wording to "credentials obtained" keeps the scoring description consistent with the required fields and examples.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 32 out of 33 changed files in this pull request and generated 3 comments.
Files not reviewed (1)
- modules/terraform-azure-kali/.terraform.lock.hcl: Generated file
Comments suppressed due to low confidence (1)
cli/internal/variant/securestring.go:113
- decryptSecureString() splits the inner payload into 3 fields but doesn’t validate the SecureString version field. If the blob isn’t version 2, the function may produce a misleading downstream error (or decode garbage). It’s better to explicitly require
parts[0] == "2"and return a clear error when it doesn’t match.
// Format: "2|iv_base64|ct_hex" (version 2 with AES key).
parts := strings.SplitN(inner, "|", 3)
if len(parts) != 3 {
return "", fmt.Errorf("expected version|iv|ct format, got %d parts", len(parts))
}
The old cleanup only matched 14 file extensions, missing agent artifacts with no extension (sam, system), unusual extensions (.hash, .asp, .pcap, .py, .log), python tempfiles (tmp*), and directories (dpapi_pamela, gpo_files, netlogon, spider_*). Now deletes all non-hidden files and non-system directories in /tmp. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…RM checks Migrate 20 runPS() callers to runPSErr() so SOCKS/WinRM transport failures emit WARN with the actual error instead of being misinterpreted as "resource not found" (false FAIL) or "firewall disabled" (false PASS). Also promotes IIS upload missing-directory from INFO to FAIL since it's a real vuln gap. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove docs/design-live-verification.md (internal draft, not needed in repo). Replace hardcoded /home/ssm-user/mkultra/ and /home/kali/mkultra/ paths with $HOME across range-cleanup docs and score_reset.go. Add Azure range-cleanup doc. Fix standalone.exe typo, chunked PS script upload for WinRM 8191-char limit, filepath.Base Windows path handling, and hashcat potfile cleanup target. Co-Authored-By: Claude <noreply@anthropic.com>
- Fix stuttered VerifyReport godoc comment - Extract tryAdminCheck helper to deduplicate local-auth fallback - Wrap bare errors in WriteAnswerKey with fmt.Errorf %w - Replace context.Background() with cmd.Context() in bastion commands - Extract bastionWithVM helper, fix ctx param order in resolveRoleDefaults - Add mustRandInt to panic explicitly on crypto/rand failure - Convert ACL check from fmt.Sprintf to renderScript/psq template - Precompile word-boundary regexes in buildOrderedReplacements - Return error from copyFile instead of swallowing silently - Add instance/command ID to SSM error messages - Add comments for evidence length threshold and substring match - Validate all PKCS7 padding bytes, warn on odd-length UTF-16LE - Return actual DeleteReport error in runRestart - Rename shadowed any builtin to found in checks.go - Simplify isLocalAccount else-after-return Co-Authored-By: Claude <noreply@anthropic.com>
…ation, and crypto Covers core scoring logic (extractNTHash, verifyEvidence, matchCredential, ParseReport), live verification command building (nxc, secretsdump, shellQuote), scoring helpers (dcIPForDomain, hostnameMatches, nil LiveVerifier), answer key generation edge cases, and SecureString crypto error paths and roundtrip edge cases (empty/unicode passwords). Co-Authored-By: Claude <noreply@anthropic.com>
…e cmd.ErrOrStderr - Add cloud-init-kali.yaml.tpl (referenced by main.tf but not committed) - runRestart: warning on delete failure is now non-fatal (return nil) - score.go: replace os.Stderr with cmd.ErrOrStderr() for testability Co-Authored-By: Claude <noreply@anthropic.com>
…ion prefix Co-Authored-By: Claude <noreply@anthropic.com>
233f2ee to
bb08292
Compare
- Run gofmt on 6 files flagged by pre-commit - Add explicit error discards for stderr fmt.Fprintf calls (errcheck) - Trim double trailing newlines in agent prompt markdown files Co-Authored-By: Claude <noreply@anthropic.com>
…-critic) - Refactor scoreHosts (29→12), scoreDomains (19→8), scoreCredentials (16→7) by extracting helpers: scoreOneHost, tryHostFindings, tryAdminUserFindings, markHostVerified, scoreOneDomain, collectDCSyncCandidates, scoreSingleCredential, tryLiveAuth - Fix errcheck: discard fmt.Fprintf return values for stderr writes - Fix staticcheck QF1012: use fmt.Fprintf(&sb, ...) instead of sb.WriteString(fmt.Sprintf(...)) - Fix staticcheck S1011: replace loop with append(slice, items...) - Fix go-critic ifElseChain: convert to switch statements - Remove unused isTruthy function from generate.go Co-Authored-By: Claude <noreply@anthropic.com>
- Fix 4 remaining WriteString(Sprintf) -> fmt.Fprintf in score_reset.go - Extract removePKCS7Padding from decryptSecureString (16->11) - Extract transformConfigJSON from transformFile (16->10) - All gocyclo violations now resolved (0 functions over 15) Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Run pre-commit locally to resolve CI failures from auto-fix hooks: - markdownlint: add blank lines before lists (MD032) - terraform validate: regenerate lock file hashes for current platform Co-Authored-By: Claude <noreply@anthropic.com>
…lures - checks.go: quote the $target value inside Get-ADObject -Filter to fix AD filter parse errors on names containing dots or spaces (e.g. "brenda.rogers", "Domain Admins") — was producing 14 false WARN results - shell_bastion.go: base64-encode commands before passing through Azure Bastion SSH to prevent long/multi-line scripts from being mangled by intermediate shell argument parsing - score_reset.go: remove outer parens from the agent-report find command to avoid $( ( ... ) ) being parsed as arithmetic expansion $(( ... )), which crashed the Kali cleanup script with division-by-zero Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Kali marketplace image doesn't ship nxc or impacket, causing live verification (host admin checks, DCSync) to silently fail. Add packages and a secretsdump.py wrapper to cloud-init so future VMs are ready for scoring out of the box. Co-Authored-By: Claude <noreply@anthropic.com>
ACL probes sometimes returned empty stdout through the SOCKS5/WinRM tunnel, causing false WARN results (14 of 19 ACLs). The existing runPSErr retries weren't sufficient under concurrent load. Add a check-level retry loop (up to transientRetries with backoff) that re-runs the probe when output lacks any expected marker. Co-Authored-By: Claude <noreply@anthropic.com>
Standalone
dreadgoad scorecommand that tests agent-reported credentials against the running GOAD lab via nxc/secretsdump on the Kali attack box, replacing the old inference engine. Also adds ascore resetsubcommand for cleaning up between agent runs.Added
dreadgoad scorecommand — scores an agent JSONL report against the answer key with optional--live-verifyfor authoritative resultsdreadgoad score generate-key— moved fromscoreboard generate-key(hidden alias kept)dreadgoad score reset— cleans Kali attack box (nxc databases, tickets, NTDS dumps, Responder logs, Dreadnode sessions) and Windows hosts (webshells, share drops, temp scripts, registry dumps) between agent runs. Supports dry-run mode, rogue AD computer account purge, group membership diff, and attack tool exe blacklistnxc smb(auth check[+], admin check(Pwn3d!)) andsecretsdump.py -just-dc-user(DCSync proof)ShellRunnerinterface with AWS SSM and Azure Bastion SSH implementationsRole=AttackBoxtag), and SSH key from environment tags — zero flags needed with-p azuredocs/scoring.mduser-facing documentation anddocs/design-live-verification.mddesign docnetbios_namefield in answer key domain objectives for correct secretsdump-just-dc-usertargetingscoreboard/agent_prompt-2.md) with informal/defensive framingChanged
live_auth(ACL abuse targets whose passwords may change) vspassword_match(static comparison) based on config ACL edgesdreadgoad score --live-verifyfor verified resultskaliKeyPath()moved fromcli/cmd/bastion.goto exportedazure.KaliKeyPath()incli/internal/azure/kali.goscoreboard/agent_prompt.md) restructured: generic examples,hostnamerequired for host findings, report path changed from/tmp/report.jsonlto./report.jsonlBreaking
generate-keyoutput drops from ~69 to ~38 objectives. Techniques were redundant with credential/host/domain proof and suffered from ID alignment issues between agent and answer key.inferHosts(),inferDomains(),inferTechniques()and ~170 lines of credential-graph inference deleted fromverify.go. The scoreboard TUI now shows 0 for hosts/domains (static-only); usedreadgoad score --live-verifyfor those.Fixed
secretsdump -just-dc-userno longer fails withERROR_DS_NAME_ERROR_NOT_FOUND— was passing the FQDN (hq.deltasystems.local/krbtgt) instead of the NetBIOS name (HQ/krbtgt)[+]false positives from informational output lines and(Guest)fallback auth are now filtered@or:no longer break impacket'suser:password@hostparsing — always uses-hasheswith computed NT hashcmd.Stdin = nil)powerkingftw135) into variant deployments viasecret.ps1in SYSVOL--local-authwhen domain auth fails — fixes scoring of local Administrator SAM hashes reported with a domain FQDN target (e.g.Administrator@vortexindustries.localfrom a SAM dump)admin_userslist when findings lack ahostnametag — DA creds from DCSync can now verify host access without explicit hostname taggingbuildNXCCommandno longer passes both-dand--local-authwhich nxc rejects as mutually exclusivebash -cto only execute the first wordrunPS()callers migrated torunPSErr()so SOCKS/WinRM failures emit WARN instead of false FAIL (or false PASS for firewall). Fixes intermittent false negatives on Azure where SOCKS proxy flakiness caused checks like CertEnroll, SMB shares, domain trusts, and GPOs to report "not found" when the resource was present.-p azureNotes
generate-key— must be patched into the answer key after deployment (documented inscoring.md)