Repository navigation
v7.0.2 into main - #841
Merged
Merged
v7.0.2 into main#841
Conversation
…main-into-7.0-dev Merge private-main into 7.0-dev
…outing packet encryption (EmpireProject#1226) Replace the non-FIPS-approved ChaCha20-Poly1305 AEAD cipher with AES-256-GCM across all agent languages (PowerShell, Python, IronPython, Go) for routing packet header encryption. Wire format is unchanged (12B IV + 32B AEAD blob), so this is a drop-in protocol swap. Server-side uses the `cryptography` library's AESGCM. Agent-side provides: - Pure-Python AES-GCM (GF(2^128)/GHASH/GCTR) for Python/IronPython stagers - .NET Framework 4.x compatible C# AesGcmHelper for PowerShell stagers - Go stdlib crypto/aes + crypto/cipher for the Go agent Note: The C# agent (Sharpire) is maintained separately and must be updated independently for FIPS compliance. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eProject#1227) * feat: increase HMAC-SHA256 truncation from 10 to 16 bytes for FIPS SP 800-107 compliance FIPS SP 800-107 requires a minimum of 128 bits (16 bytes) for truncated HMAC output. The previous 10-byte (80-bit) truncation was below this minimum. This change updates the AES-CBC encrypt-then-HMAC payload encryption across all agent languages. Changes per language: - Python server (encryption.py): mac[0:10] → mac[0:16], updated verify and decrypt slicing and minimum length checks - Go agent (encryption.go): hmacValue[:10] → [:16], updated verify/decrypt - PowerShell stagers (http.ps1, http_malleable.ps1): [0..9] → [0..15], [-10..-1] → [-16..-1], updated length checks - Python stager (aes.py): same truncation and length check updates Wire format change: +6 bytes per AES-CBC encrypted payload. Breaking change: deployed agents must be re-staged. Note: The C# agent (Sharpire) must be updated separately. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add HMAC interop, edge case, and old-format rejection tests Add cross-implementation tests verifying server (cryptography lib) and agent (pure Python aes.py) produce identical 16-byte HMACs and can decrypt each other's output. Add edge case tests for old 10-byte format rejection through decrypt_and_verify, boundary condition at 32 bytes, and tampered HMAC detection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: add ruff per-file-ignores for plugins directory Suppress PLC0415, PLR0911, PLR0913, and SLF001 for empire/server/plugins/* to match the relaxed linting applied to other untested areas (listeners, modules, stagers). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ireProject#1228) * fix: replace weak key generation with CSPRNG for FIPS compliance Replace AESCipher.generate_key() which used random.SystemRandom().sample() over printable characters (without replacement, ~207-bit effective entropy) with os.urandom(32).hex() for full 256-bit CSPRNG entropy. The key is now returned as a 64-character hex string, matching the format used by DH-derived session keys (serverPub.key.hex()). This also fixes a latent bug where the old printable-char keys were incompatible with bytes.fromhex() used in agent_communication_service.py. Removed unused `import random` and `import string` from encryption.py. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add end-to-end roundtrip test for generate_key through bytes.fromhex Verifies the full consumer path: generate_key() → bytes.fromhex() → encrypt_then_hmac() → decrypt_and_verify(), matching how agent_communication_service.py uses the generated key. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Update C# YAMLs and install for Empire-Compiler dev branch (EmpireProject#1186) * Update C# module/stager YAMLs and install scripts for Empire-Compiler dev branch - Remove ReferenceAssemblies from all C# module and stager YAMLs (compiler now auto-resolves them) - Update EmbeddedResources Location paths to use net40/ and common/ subdirectories - Add .NET 10 SDK installation to setup/install.sh and Dockerfile Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update Empire-Compiler to v1.0.0-a.1 pre-release Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix launcher.txt path for new compiler directory layout and add dotnet SDK to CI - Update stager_generation_service.py to write launcher.txt to common/ subdirectory - Fix CSharpPy.yaml launcher.txt Location to use common\ prefix - Add .NET 10 SDK installation step to CI workflow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Omit empty ReferenceAssemblies/EmbeddedResources from compiler YAML to enable auto-resolve When ReferenceAssemblies or EmbeddedResources are empty, omit them from the compiler YAML rather than passing empty lists. This allows the compiler to auto-resolve reference assemblies from its AssemblyReferences directory. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix test config to use v1.0.0-a.1 compiler and revert unnecessary changes The CI BOF test was failing because test_server_config.yaml pointed to the old v0.4.1 compiler which doesn't support auto-resolve. Updated to v1.0.0-a.1. Reverted the dotnet SDK CI step and module_service changes as they are not needed — the compiler is self-contained. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Address PR feedback: revert dotnet from Dockerfile, gate install behind -c flag, simplify write_text - Remove dotnet SDK from Dockerfile (compiler is self-contained) - Only run install_dotnet when -c (compile from source) flag is passed - Use write_text() instead of open/write for launcher.txt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: restore Empire-Compiler ref to v1.0.0-a.1 after merge from private-main The merge from private-main overwrote the compiler version back to v0.4.4, which lacks embedded reference assemblies and fails C# compilation with "System.Object is not defined" errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: extract launcher resource helper, improve install_dotnet error handling, add path consistency test - Extract _write_launcher_resource helper to eliminate duplicated launcher.txt write logic and add encoding="utf-8" for consistent encoding behavior - Improve install_dotnet: download-then-execute pattern instead of curl|bash, post-install verification, idempotent rc appends, symlink failure warning - Fix COMPILE_FROM_SOURCE comparison from -eq to == for string consistency - Add parametrized test verifying YAML EmbeddedResources paths match the service write path to prevent silent path drift regressions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: update Empire Compiler to v1.1.0-a.1 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: version-aware compiler cache and download timeout The cache check now includes the configured ref (version tag) so that upgrading the compiler ref actually downloads the new version instead of reusing a stale cached binary. Also adds a (30s connect, 300s read) timeout to the archive download to avoid indefinite hangs on large tarballs (~300 MB). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update stager resource paths for v1.1.0-a.1 compiler layout v1.1.0-a.1 reorganized Data/EmbeddedResources/ into subdirectories: - DLLs moved to net40/ - Shared resources (Lib.zip, launcher.txt) moved to common/ Update CSharpPy.yaml, CSharpPS.yaml, and launcher write paths in stager_generation_service.py to match the new layout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: isolate donut-shellcode calls to prevent loader.bin permission errors (EmpireProject#1234) donut-shellcode writes a `loader.bin` file to the current working directory. When a root-owned loader.bin already exists (e.g. from a previous run as root), all shellcode generation fails with "Cannot open file" because the non-root process cannot overwrite it. Extract a shared `donut_create()` utility that runs donut.create() inside a temporary directory with a threading lock for concurrency safety. All 6 donut call sites now use this wrapper: - stager_generation_service.py (3 methods) - stagers/windows/shellcode.py - modules/csharp/management/ProcessInjection.py - modules/csharp/management/ThreadlessInject.py Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add SharpHound C# module for BloodHound AD enumeration Add situational awareness module that compiles and executes SharpHound for Active Directory data collection. Supports all CollectionMethods via CLI arguments. Targets Net45 with async Main handling and all required NuGet reference assemblies. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: fix SharpHound C# module and add ILRepack merge support Remove old PowerShell bloodhound/sharphound modules. Fix SharpHound C# module to use InvokeSharpHound entry point and enable ILRepack assembly merging via MergeReferences flag. Add merge_references parameter to DotnetCompiler.compile_task() and wire it through module service. Update compiler ref to v1.1.0-a.2 and adjust test module count. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address silent failures, crypto safety, and security issues from PR review - Use hmac.compare_digest for constant-time HMAC verification instead of double-HMAC == - Narrow ChaCha20Poly1305 exception catches from Exception to InvalidTag - Replace bare except clauses in aesgcm.py with specific exception types - Add error reporting in process_tasking instead of silent swallow - Fix dead length < 0 check with proper bounds validation in agent parse_routing_packet - Add diagnostic logging on AEAD tag verification failure in agent - Add filter='data' to tar.extractall() to prevent path traversal - Harden donut_util CWD restoration with fallback on missing directory - Return error messages when donut shellcode generation returns None - Log compiler stderr at WARNING level instead of INFO Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: fix ruff formatting in stager_generation_service.py Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: update Empire Compiler to v1.1.0-a.3 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: update changelog for SharpHound, ILRepack, compiler v1.1.0-a.3, and review fixes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…#1235) * fix: downgrade compiler args log to debug and set csharp modules to background Compiler args were logging at INFO level, flooding server logs with base64-encoded YAML payloads. Changed to DEBUG so they only appear when debug logging is enabled. Updated all csharp modules to run as background tasks. Removed redundant Agent option from PatchETW and PatchlessAMSI modules. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: repair VNC module code and add C# compilation test VNC module had copy-pasted ThreadlessInject code instead of using the NVNC library — it was non-functional since introduction. Replaced with correct NVNC.VncServer integration. Renamed misleading Username option to ServerName (it's the VNC session display name, not an auth credential). Added parametrized compilation test (tests/test_compile_csharp.py) that compiles every C# module against the real EmpireCompiler binary. Runs with --runslow, one test per module for visible progress. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: apply ruff formatting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update tests to reflect csharp modules running as background jobs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…reProject#1236) * fix: replace bcrypt with PBKDF2-HMAC-SHA256 for FIPS SP 800-132 compliance bcrypt is not a FIPS-approved algorithm. Migrate password hashing to PBKDF2-HMAC-SHA256 (600k iterations, 16-byte salt) which is FIPS SP 800-132 compliant. Legacy bcrypt hashes are transparently migrated to PBKDF2 on next successful login. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: improve PBKDF2 error handling and constant documentation - Add logging to verify_password catch blocks for DB corruption detection - Wrap bcrypt legacy path in try/except for corrupted hashes - Wrap bcrypt migration in try/except so login succeeds even if migration fails - Add audit log for successful bcrypt->PBKDF2 migrations - Document defaults.py constant dependency on jwt_auth parameters Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add PBKDF2 password hashing changelog entry Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: remove bcrypt migration path (major version, clean break) No need to support legacy bcrypt hashes since this targets a major release. Remove _is_bcrypt_hash(), bcrypt import, migration logic in authenticate_user(), and bcrypt migration tests. The PLC0415 lint exception for jwt_auth.py is no longer needed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: review round 2 — changelog, bcrypt dep, hash validation - Fix CHANGELOG to reflect clean break (no bcrypt migration) - Remove bcrypt dependency from pyproject.toml - Add algorithm and minimum iteration validation in verify_password to prevent crafted low-iteration hashes from DB tampering Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: expand PBKDF2 test coverage from review feedback - Test bcrypt hash rejection (clean break verification) - Test defaults.py hash verifies with jwt_auth.verify_password (interop) - Test empty, unicode, and long (>72 byte) password round-trips - Expand malformed hash cases (invalid hex, non-integer iterations) - Add module docstring explaining client fixture dependency Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: review round 3 — iteration ceiling, error diagnostics - Add upper bound (2M) on iteration count to prevent algorithmic DoS from crafted DB values - Remove dead IndexError catch, use ValueError only - Include hash prefix and exc_info in error log for debugging - Add diagnostic log.exception to get_password_hash for OpenSSL failures Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: regenerate poetry.lock after bcrypt removal Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…oject#1237) * fix: replace MD5 with SHA-256 for staging key normalization MD5 is not FIPS-approved. When a staging key is not exactly 32 characters, normalize it using SHA-256 (truncated to 32 hex chars) instead of MD5. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: improve staging key normalization log message - Redact staging key value from log output (security) - Add breaking change warning about SHA-256 vs MD5 algorithm change - Clarify SHA-256 truncation in inline comment Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add SHA-256 staging key changelog entry Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add staging key SHA-256 normalization tests Unit tests for _normalize_listener_options covering: short key normalization, 32-char passthrough, whitespace stripping, long key, lowercase hex output, and MD5 regression guard. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: reject empty staging key, improve log diagnostics - Raise ValueError for empty/whitespace-only StagingKey - Include input length in log warning for debugging - Add tests for empty and whitespace-only key rejection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: simplify staging key normalization warning message Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: shorten staging key warning message Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Project#1238) * fix: replace random module with secrets for CSPRNG compliance Python's random module uses Mersenne Twister which is not cryptographically secure. Replace with secrets module (CSPRNG) for staging key generation, keyword obfuscation, and session ID generation per FIPS SP 800-90A. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add CSPRNG replacement changelog entry Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: migrate helpers.random_string to secrets.choice for nonce generation helpers.random_string is called for nonce generation in agent_communication_service.py — same file we already patched for session IDs. The character selection must use CSPRNG, not Mersenne Twister. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…oject#1240) * refactor: remove ChaCha20-Poly1305 code (non-FIPS-approved) ChaCha20-Poly1305 is not FIPS-approved. Routing packets were already migrated to AES-256-GCM. Remove the now-unused Poly1305, ChaCha, and ChaCha20Poly1305 classes from encryption.py, the agent-side chacha.py stager, and associated tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add ChaCha20 removal changelog entry Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: remove ChaCha20 reference from packethandler docs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: harden TLS configuration for FIPS SP 800-52r2 compliance - Enforce TLS 1.2 minimum (remove dead Python 2 SSLv23 fallback) - Remove non-GCM and non-ECDHE cipher suites from JA3 evasion pool - Specify RSA 4096-bit key for self-signed certificate generation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use secrets.choice for cipher selection and fix broken test - Replace random.choice with secrets.choice for consistency with CSPRNG branch - Update TestGenerateRandomCipher to match new single-cipher return format - Returns docstring note: single cipher string, not colon-separated pair Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add TLS hardening changelog entry Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: strengthen cipher suite tests - Run 50 iterations to verify both FIPS ciphers appear (not just one) - Add test that cipher string is accepted by OpenSSL's set_ciphers() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: return both FIPS ciphers in randomized order for availability A single cipher could fail on restricted OpenSSL builds. Return both FIPS-approved suites colon-separated with randomized order so OpenSSL can negotiate even if one is unavailable, while still providing JA3 evasion via order randomization. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ireProject#1241) * feat: replace raw SHA-256 with HKDF-SHA256 for DH key derivation FIPS SP 800-56C requires a proper KDF for deriving keys from shared secrets. Replace raw SHA-256 hashing with HKDF-SHA256 (RFC 5869) using domain separation info "empire-session-key" across all 5 implementations: Python server, Python/IronPython stager, Go agent, and PowerShell stagers. Also normalizes shared secret byte encoding to 768 bytes (6144-bit prime / 8) per NIST SP 800-56A across all implementations. BREAKING: All deployed agents must be re-staged after this change. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Go DH Legendre symbol bug, C# HKDF signature, dead code cleanup - Fix CheckPublicKey to use (prime-1)/2 exponent (Legendre symbol) instead of (prime-1) (Fermat's little theorem which always returns true) - Remove unused length parameter from C# HkdfSha256 (only supports 32 bytes) - Add RFC 5869 reference to C# HKDF comments - Remove dead commented-out SHA-512 code in Go Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add HKDF key derivation changelog entry Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: update agent docs for HKDF key derivation and AES-GCM routing - Update staging process diagram and description to reference AES-256-GCM and HKDF-SHA256 instead of generic AES/HMAC and raw DH - Add FIPS SP references (800-56C, 800-107) to protocol description - Update stageclass.md to reference HKDF-derived session key Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add HKDF known-answer vector for cross-language verification Pins HKDF output for shared_secret=42 normalized to 768 bytes. Verifies server (cryptography lib) and agent stager (hand-rolled hmac) produce identical keys. The hex value can be used as a test vector in Go and C# agent tests. Reverts compiler version bump to v1.1.0-a.3 (tag v1.1.0-a.4 does not exist yet). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…mpireProject#1243) * fix: replace remaining random module with secrets and MD5 with SHA2 (EmpireProject#1242) Replace all remaining uses of Python's non-FIPS `random` module with `secrets` (CSPRNG) across listeners, stagers, agents, and utilities. Replace MySQL MD5() with SHA2() in the hosts table generated column, including migration for existing databases. Fix pre-existing taskUri variable name mismatch in http_malleable generated agent code. BREAKING: Deployed agents must be re-staged (agent source code changed). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update Empire Compiler to v1.1.0-a.4 for FIPS-compliant Sharpire The bundled Sharpire in v1.1.0-a.3 still uses 10-byte HMAC truncation, raw SHA-256 key derivation, and 6147-byte shared secret normalization, causing HMAC verification failures during C# agent staging. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use random.SystemRandom() instead of secrets in IronPython agent IronPython does not include the secrets module (CPython 3.6+). Use random.SystemRandom() which wraps os.urandom and is available in IronPython. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: revert stager templates from secrets to random.SystemRandom() Stager templates are shared between Python and IronPython agents. IronPython lacks the secrets module, so use random.SystemRandom() which wraps os.urandom and is available in both runtimes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…dening (EmpireProject#1244) * fix: replace remaining random module with secrets and MD5 with SHA2 (EmpireProject#1242) Replace all remaining uses of Python's non-FIPS `random` module with `secrets` (CSPRNG) across listeners, stagers, agents, and utilities. Replace MySQL MD5() with SHA2() in the hosts table generated column, including migration for existing databases. Fix pre-existing taskUri variable name mismatch in http_malleable generated agent code. BREAKING: Deployed agents must be re-staged (agent source code changed). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update Empire Compiler to v1.1.0-a.4 for FIPS-compliant Sharpire The bundled Sharpire in v1.1.0-a.3 still uses 10-byte HMAC truncation, raw SHA-256 key derivation, and 6147-byte shared secret normalization, causing HMAC verification failures during C# agent staging. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use random.SystemRandom() instead of secrets in IronPython agent IronPython does not include the secrets module (CPython 3.6+). Use random.SystemRandom() which wraps os.urandom and is available in IronPython. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: revert stager templates from secrets to random.SystemRandom() Stager templates are shared between Python and IronPython agents. IronPython lacks the secrets module, so use random.SystemRandom() which wraps os.urandom and is available in both runtimes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: review followups — remove dead code, cache CSPRNG, harden migration - Remove dead `getIV()` function from aes.py (unused, bypassed by inline `os.urandom(16)` in `aes_encrypt()`) - Cache `random.SystemRandom()` as module-level `_sysrand` in agent stager comms, IronPython agent, and malleable generated code to avoid per-call instantiation in hot loops - Deduplicate SHA2 column expression in MySQL migration - Handle empty-string `gen_expr` edge case (non-generated column) - Improve log message to specify SHA2-256 variant Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: move _sysrand after imports, use standard SHA-256 name in log - Move _sysrand assignment after all imports in ironpython_agent.py (was splitting the import block between re and shutil) - Change log message from nonstandard "SHA2-256" to "SHA-256" per NIST FIPS 180-4 naming convention Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: drop MD5 migration path for hosts.unique_check 7.0 is a major release — no need to support migrating from MD5. Fresh databases get SHA-256, existing databases with the column are left as-is. Users upgrading should use --reset-db. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: update CHANGELOG for FIPS RNG/MD5 changes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…1246) * refactor: move test_compile_csharp.py to empire/test/ Consolidate C# compilation test into the canonical test directory. Fix path constants to resolve correctly from the new location. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: apply ruff formatting to test_compile_csharp.py Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Separates the unreleased changelog entries into two distinct sections: - [7.0.0] - Unreleased: FIPS/crypto work (AES-GCM, HKDF, bcrypt replacement, CSPRNG, TLS hardening, SharpHound, compiler updates) - [6.6.0] - Unreleased: private-main features (DB pool fixes, event loop blocking, Alembic migrations, Wave 1 modules, obfuscation improvements) Also bumps VERSION and pyproject.toml from 6.5.0 to 7.0.0 for the dev branch. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…changelog-split Bump version to 7.0.0 and split changelog
…mpireProject#1262) The HMAC increase from 10 to 16 bytes (EmpireProject#1227) introduced an off-by-one in the minimum ciphertext size check. A task packet like TASK_DIR_LIST with path "/" produces exactly 48 bytes of encrypted data (16 IV + 16 CT + 16 HMAC), which was rejected by the `-le 48` / `> 48` guards. The agent's Decrypt-Bytes returned $null, so no response was ever sent. Fix the boundary in all four decrypt sites: - PowerShell: `-le 48` → `-lt 48` - Python: `> 48` → `>= 48` Also narrow the TASK_DIR_LIST exception handler from `except ValueError` to `except (UnicodeDecodeError, json.JSONDecodeError, KeyError)` with logging, so data-format errors are caught without swallowing database exceptions that should propagate. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ct#1261) * feat: chunked file uploads to agents, removing 1MB limit Large files are split into 512KB chunks and drip-fed one-at-a-time across agent checkins, preventing agent memory lockup on large uploads. Server reads chunks lazily from disk via seek to avoid holding the entire file in memory. Supports all agent languages (PowerShell, Python, IronPython, Go) with backwards-compatible protocol detection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: harden chunked upload protocol and agent packet handling - Add upload_id to prevent race condition where error paths cancel a different upload started for the same agent - Replace brittle "successful" substring match with startswith prefix check for chunk continuation, and add missing [*] prefix to Go agent - Fix latin-1 encoding roundtrip in Python agent packet handler to prevent struct header corruption on large task data - Guard process_job_tasking against phantom GETs (matching process_tasking) - Replace empty except-pass with diagnostic print in process_tasking Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nd stager-template API responses (EmpireProject#1266) When only one listener is deployed (the common case), modules and stager templates now return the first active listener's name as the default value for Listener-type options, and all active listener names in suggested_values. Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ct compiler target for C# modules (EmpireProject#1267) * feat: add dotnet_version column to agents table Adds migration to create dotnet_version column in agents table and updates the Agent SQLAlchemy model to include the new field for tracking .NET runtime versions on C# agents. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat: add dotnet_version_util normalization utility Create normalize_dotnet_version() function to handle inconsistent .NET version formats (Net35, net35, 3.5, etc.) into canonical forms (net35-net48). Includes comprehensive test suite for all variant inputs and edge cases. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat: extend sysinfo with .NET Framework version detection (field 14) Append a 14th pipe-delimited field to the sysinfo string: - http.ps1 and http_malleable.ps1: detect installed .NET Framework via HKLM registry keys and emit net35/net40/net45/net46/net47/net48 or "" - get_sysinfo.py: add trailing | so Python/Linux agents produce a 14th empty field when split on |; old 13-part agents remain compatible Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix: add .NET version detection to agent.ps1 and trailing pipe to ironpython_agent.py - agent.ps1 Get-Sysinfo: insert identical .NET registry detection block after PROCESSOR_ARCHITECTURE (matches http.ps1 implementation) - ironpython_agent.py get_sysinfo: add trailing | to produce empty 14th field consistent with get_sysinfo.py approach Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat: parse and store dotnet_version in agent_communication_service Parse parts[13] in both binary and string sysinfo parsers, pass dotnet_version to update_agent_sysinfo(), store via normalize_dotnet_version(), and include DotNet Version in the checkin log output. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix: cosmetic improvements to agent_communication_service - Rename variable from 'normalised' to 'normalized' (American English, 3 occurrences) - Update log line to display normalized DotNet version matching DB storage Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat: add .NET version auto-selection to _validate_module_params Inserts a DotNetVersion resolution block in ModuleService._validate_module_params for C# modules: prefers explicit user choice, then exact agent match, then highest compatible downgrade, then module default; raises ModuleValidationException when no compatible version exists for the agent. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix: improve .NET version auto-selection robustness - Fix 1: Raise ModuleValidationException when user-supplied DotNetVersion is invalid instead of silently falling through to agent/default selection. This prevents silent failures when an invalid version is explicitly requested. - Fix 2: Extract VERSION_ORDER constant to dotnet_version_util.py for reusability and to reduce duplication. Remove local _VERSION_ORDER list from module_service.py and import from util module. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat: expose dotnet_version in agent API DTO Add dotnet_version field to Agent Pydantic model and pass it through in domain_to_dto_agent() conversion function. This exposes detected .NET version to API consumers like Starkiller UI. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix: correct .NET auto-selection propagation and registry thresholds - Write selected version to options dict (returned by _validate_module_params) so the compiler actually receives the auto-selected version, not just params - Fix .NET 4.6/4.7 release key thresholds: use 393295 (4.6.0) and 460798 (4.7.0) instead of 394802 (4.6.2) and 461808 (4.7.2) per Microsoft documentation - Fix agent.ps1 detection block indentation to match surrounding 8-space style Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review findings for .NET version detection - Add trailing pipe to agent.py and gopire sysinfo (agent.py, tasks.go, main.template) so all agent types produce consistent 14-field sysinfo - Bump min-parts guard from < 12 to < 13 in both sysinfo parsers - Remove redundant params["DotNetVersion"] write (only options dict matters) - Update sysinfo field-layout comments to document new dotnet_version field - Add 8 unit tests covering all _validate_module_params auto-selection branches: fallback, exact match, downgrade, impossible downgrade, user choice, and option validation rejection Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * feat: always select highest compatible .NET version for C# modules Previously, the fallback (no agent info) picked CompatibleDotNetVersions[0] (the lowest/first entry). Now all paths pick the highest compatible version: - No agent info → highest version in CompatibleDotNetVersions - Agent version known → highest compatible version ≤ agent's installed version - Explicit user choice → still honoured as before Simplifies the two-branch exact-match/downgrade logic into a single sorted candidates list, making the intent clear. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix: capture user-supplied DotNetVersion before validate_options injects defaults validate_options mutates params by filling in the module's default value (CompatibleDotNetVersions[0], e.g. "Net35") when the key is absent. Our auto-selection code then saw this injected default as a user-supplied value, treating it as an explicit choice and bypassing the highest-version logic. Fix: read params["DotNetVersion"] before validate_options runs and use that snapshot for the user_val check. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix: enforce CLR family boundaries in .NET version detection and selection CLR 2.0 (net35) and CLR 4.0 (net40+) are incompatible runtimes. Updated PowerShell detection to independently probe both CLR families and report comma-separated results (e.g. "net48,net35"). Added parse_agent_dotnet_versions() helper, CLR_2_VERSIONS/CLR_4_VERSIONS constants, and updated module_service to respect CLR family boundaries when auto-selecting DotNetVersion. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix: default DotNetVersion option to highest compatible version on load Previously CompatibleDotNetVersions[0] (the lowest/first entry, e.g. Net35) was used as the static module default. Now the highest recognized version (e.g. Net40 for Seatbelt) is used, so Starkiller shows the right default even before any agent-specific selection runs. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix: idempotent migration and test_alembic update for 0002 Migration 0002 now guards against duplicate column errors by checking if dotnet_version already exists before ALTER TABLE — handles fresh installs where ORM create_all has already created the column. test_alembic.py: - Bump temp test migrations from "0002" to "0003" (down_revision "0002") to avoid "Multiple head revisions" conflict with our real 0002 migration - Update version assertions: after migrate_db(), head is "0002" not "0001" - test_startup_does_not_restamp_tracked_db: check revision is not None rather than hardcoding "0001" (prior tests may advance to "0002") Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * refactor: extract _resolve_dotnet_version into standalone testable method Splits the C# DotNet version resolution logic out of _validate_module_params into a static method, reducing complexity (PLR0912) and enabling unit testing without module/agent mocks. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * chore: remove stale PLR0912 noqa after refactor Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ect#1274) * fix: csharp kerberoast activeds.dll!Init P/Invoke failure SharpSploit's DomainSearcher.ConvertADName declares activeds.dll!Init as a flat P/Invoke, but Init is an IADsNameTranslate COM method, not a DLL export; every invocation threw EntryPointNotFoundException on modern Windows. Because SharpSploit is vendored inside EmpireCompiler, patch the Empire wrapper to sidestep the broken path: build an LDAP filter from the Usernames option and call Kerberoast((IEnumerable<string>)null, ldapFilter) so SharpSploit's ConvertIdentitiesToFilter short-circuits before ConvertADName is reached. Also hardens the wrapper: - Refuse to run when supplied usernames parse to zero valid sAMAccountName tokens, instead of silently widening to a domain-wide roast. - LastIndexOf('\\') for DOMAIN\ stripping so doubled backslashes from the tasking pipeline do not leak into the filter. - RFC 4515 escaping on sAMAccountName tokens. - Emit a [*] diagnostic line when zero tickets come back, showing the filter that was used. - Fix stale "port scan" copy-paste in the Usernames option description. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: drop Net35-incompatible IsNullOrWhiteSpace in Kerberoast wrapper Previous fix introduced string.IsNullOrWhiteSpace, a .NET 4.0+ API. Kerberoast.yaml declares CompatibleDotNetVersions [Net35, Net40], and test_compile_csharp_module compiles against the first entry, so CI hit CS0117 on Net35. Swap for the Net35-safe idiom (!IsNullOrEmpty && Trim length > 0) already used by the sibling SharpSploit modules in this directory. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ect#1272) * feat: pluggable credential parsers for non-mimikatz tools Adds a dispatch layer so modules can declare a credential_parser in their YAML and have their output auto-ingested into the credentials table. Previously only Invoke-Mimikatz output was parsed (via a first-line `Hostname:` heuristic in helpers.parse_credentials); everything else from Rubeus/Kerberoast/DPAPI/SessionGopher/etc. sat unparsed in task logs. Changes ------- - New package `empire/server/common/credential_parsers/` with 12 parsers (mimikatz, prompt, kerberoast, rubeus, pwdump_hashes, sharp_dpapi, session_gopher, internal_monologue, sharpsecdump, ntlmextract, tgtdelegation) behind a `CredentialParser` Protocol and a registry. - New credtypes: netntlmv1, dcc2, krbtgs, krbasrep, krb_ticket, dpapi_masterkey, dpapi_system_key, dpapi_vault_cred. `hash` and `plaintext` are preserved as-is. No DB migration — structured data (DPAPI vault trios, tgtdelegation AP-REQ + session key) is JSON-packed into the existing `password` column. - `EmpireModule` gains an optional `credential_parser: str` field; 22 credential-producing YAMLs wired up. - `agent_communication_service._ingest_credentials` replaces three duplicated call sites that each built `CredentialPostRequest` inline. Drive-by fix: delete a buggy second mimikatz parse in the _JOB branch that was double-ingesting every credential. Parser exceptions are caught so a bug in any parser cannot break the agent comms loop; modules with an unknown parser name fail loudly instead of silently falling through. - `helpers.parse_credentials` / `parse_mimikatz` kept as thin shims over the registry for backward compat. `test_helpers.py` assertion updated from bytes to str — the mac-prompt branch used to return bytes while every other branch returned str; shim normalizes to str. - 36 new unit tests + 3 integration tests driving `_process_agent_packet` end-to-end. Fixtures captured from real tool runs. Full test suite: 894 passed, 84 skipped, 0 failed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: correct credential-parser docstrings from review feedback Comment analyzer flagged four factually inaccurate docstrings and one misleading wording in the credential-parser code. All pure-docstring changes — no behavior change. - credtypes.py: HASH description mentioned a "pwdump line fragment" which nothing in this PR emits; DPAPI_MASTERKEY claimed "SHA1 of GUID : hexkey" but the stored form is "<guid>:<sha1_hex>" (the raw GUID, not a hash of it); KRB_TICKET didn't mention the tgtdelegation JSON envelope variant. - mimikatz.py: dedup comment claimed parity with legacy uniquify_tuples, but the new code keys on the full 6-tuple (including host and sid) so identical creds on different hosts survive; clarified. - rubeus.py: class docstring oversold coverage (triage has no kirbi payload, yet was listed as supported). Scoped to the subcommands the parser actually handles. - agent_communication_service.py: "_ingest_credentials ... other errors are surfaced" was misleading since they are log-only, not raised or returned. Made explicit. - helpers.py: module-level docstring index still advertised parse_credentials / parse_mimikatz as current functions; now flagged DEPRECATED with pointers to the replacement registry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor: remove helpers.parse_credentials / parse_mimikatz shims The shims were kept as a bridge for out-of-tree callers but we don't actually support that surface — any plugin still calling them would be referencing undocumented internals. Drop them outright along with their private helpers (_BareAgent, _dto_to_legacy_tuple) and the two TestParseCredentials cases that only existed to exercise the shims. New code path is the only path: empire.server.common.credential_parsers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: harden credential ingestion dispatch against partial failures Addresses three HIGH-severity findings from a post-merge review pass: N1 — `_ingest_credentials`'s docstring promised "any other error is logged at warning level but does not abort ingestion or propagate," but the `create_credential` call was outside the try/except guard. A DB IntegrityError / DataError would kill the agent comms loop for that task response. Wrap the per-credential persist call so one bad row aborts this batch only, not the whole response path. N2 — Added an `isinstance(parsed, list)` guard on the parser's return. A future parser bug returning None/str/dict would otherwise either raise inside the loop or silently iterate characters. Now caught and logged with the offending parser class name. N3 — `EmpireModule.credential_parser` was validated only at ingestion time; a YAML typo would load cleanly and silently disable credential capture for that module until the first successful task. Added a pydantic `field_validator` that rejects unknown parser names at module-load time. Module loading now fails loudly on a typo instead of silently dropping credentials six months later. Also adds `credential_parsers.registered_parser_names()` as a public accessor so the validator doesn't reach into `_REGISTRY` directly, plus a unit test for the new validator. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eProject#1273) Resolves the deprecation warnings that surface during `./ps-empire test`: - JWT `datetime.utcnow()` → `datetime.now(UTC)` - `asyncio.iscoroutinefunction` → `inspect.iscoroutinefunction` in hook dispatch - pyparsing `escChar`/`searchString` → `esc_char`/`search_string` in malleable profile parsing - Pydantic v1 `class Config` → v2 `ConfigDict` in PluginHolder - Removed defunct Pydantic v1 `Field(env=[...])` kwarg on `DatabaseConfig.use`; legacy `DATABASE_USE` env support is already handled by the existing `map_legacy_database_use_env` validator - Starlette `HTTP_422_UNPROCESSABLE_ENTITY` → `HTTP_422_UNPROCESSABLE_CONTENT` across the test suite Also refactors `ModuleService._generate_script` to return data directly (or raise `ModuleValidationException`/`ModuleExecutionException`) instead of wrapping every branch — success and error — in a `(data, err)` tuple. Before this change the tuple-return deprecation warning at module_service.py:156 fired on every successful module execution; now it fires only for legacy custom-generate modules that actually return `(None, msg)`. `execute_module`'s back-compat unpacking branch is preserved so those legacy modules still work. Behavior change: unexpected exceptions (non-`Module*Exception`) raised inside a custom-generate module's `generate()` now surface as HTTP 500 with detail "Error generating script." instead of HTTP 400. `ModuleValidationException` and `ModuleExecutionException` continue to map to 400/500 as before. Adds three tests: - `test_env_database_use_nested_wins_over_legacy` — locks in the documented precedence of `EMPIRE_DATABASE__USE` over legacy `DATABASE_USE` - `test_create_task_module_unexpected_exception` — covers the new 500 path - `filterwarnings` mark on `test_create_task_module_validation_exception`, which deliberately exercises the legacy tuple/handle_error_message path Full suite: 862 passed, 94 skipped, 3 warnings (all SQLAlchemy SAWarnings, no DeprecationWarnings). Was 857 passed, 96 warnings. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ireProject#1277) * refactor: simplify agent shell handling, move aliases to modules The PowerShell, Python, and IronPython agents each maintained ~75-150 LOC of in-process implementations of common shell commands (ls, cd, pwd, ps, ipconfig, route, whoami, hostname, etc.). This made `shell <cmd>` behave inconsistently across agent languages and coupled the four server-side output filters to fragile `task.input == "ps"` string matching. Refactor: `shell <cmd>` now passes commands straight to the system shell on every agent (matching the C# / Go agents, which already did this). Behaviors that genuinely needed to persist or return structured output move to first-class server primitives: - New TASK_CHDIR opcode (44) + REST endpoint /agents/{id}/tasks/chdir for persistent cwd changes; failure returns ERROR (0) with status=error. - Five new situational_awareness modules (processes, ipconfig, route, dir_list for PS; processes for Python) emit JSON consumed by the existing ps_filter / ls_filter / ipconfig_filter / route_filter, now retargeted from task.input to task.module_name matching. - ShellPostRequest.literal flag is now a deprecated no-op (kept for API back-compat). Hardening picked up along the way: - ipconfig_filter / route_filter gain JSONDecodeError guards mirroring ps_filter / ls_filter. - Filter early-exit on empty task.output now logs a warning instead of silently dropping. - New module YAMLs wrap their script bodies in try/catch returning "[!] <module> failed: ..." so filters see strings, not exceptions. - PowerShell Invoke-ShellCommand uses `2>&1` + try/catch so error stream reaches the operator. - IronPython run_command uses `2>&1` and surfaces ps.Streams.Error. - subprocess decode uses errors="replace" for Windows CP1252 tolerance. Tests: +10 (TASK_CHDIR roundtrip, literal no-op pin, ps_hook Python jq branch, four filter tests, empty-output guard). Total 870 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * style: ruff-format reformat per pre-commit pin (v0.12.12) Older ruff fits the next() expression on one line; local ruff 0.15.2 broke it across multiple lines. Match the pre-commit-pinned version. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Stats) (#1587) * docs: add agent-interaction screenshots * docs: expand agents.md into a workspace map with toolbar + View tab * docs: fix View-tab editability claim in agents.md Name is not the only editable field on the View tab — Delay, Jitter, Kill Date, and Working Hours are also click-to-edit in AgentForm.vue. Reword to distinguish direct-write (Name) from tasked-write (the other four) instead of contradicting the next paragraph. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: add Agent Interact page (Module/Shell/Terminal) * docs: fix Shell alias claim and ATT&CK escape in Agent Interact page * docs: document the Tasks page in agent-tasks.md * docs: fix accuracy issues in Tasks page section (agent-tasks.md) - Task ID is never offered as a column on this page (hideColumns=['id'] strips it from the picker), only Task Input is togglable - expanded row shows an input preview, not the full input, until See Full Input is clicked - image previews require clicking View Images, they are not automatic - auto-refresh on the per-agent Tasks tab comes from the workspace toolbar, not the table's own header (use-header=false) * docs: add Agent Jobs and Agent Stats pages * docs: fix Actions column accuracy and soften refresh cadence claims Agent Jobs: only killable jobs (running/started/continuous) show Kill; queued-but-active jobs show N/A; completed jobs never appear (filtered by ACTIVE_STATUSES before render, per AgentJobs.vue). Agent Stats: drop hardcoded '8s' auto-refresh interval, broaden the 'Stopped' chip description beyond repeated failures, wrap prose placeholders in code spans, and add Check Ins cadence/lookback + its empty-state string. * docs: add agent-interaction pages to the Starkiller nav --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ts (#1588) A PowerShell agent could not stage through a port_forward_pivot listener. The staging handshake broke at four distinct points, each masking the next: 1. generate_stager/generate_comms referenced self.session_cookie, which the pivot never sets -> AttributeError 500 on the stage-1 request. Now sourced from listenerOptions["Cookie"]["Value"], matching http_hop. 2. generate_stager never embedded the ed25519 staging cert keys the stage-1 templates require, so the agent's server-cert check failed and it never requested stage 2. Added _staging_cert_key_options(), which resolves the parent listener (recorded as parent_listener_name in start()) and pulls its keys, mirroring http_hop. 3. remove_lines_comments() collapsed the multi-line stager onto a single line the agent could not execute. Removed; the http listener serves the template as-is. 4. The launcher decoded stage 0 with the old RC4 model (-join[Char[]](& $R ...)) against a plaintext stage, with $R undefined, so the stager never ran. Switched to IEX ([Text.Encoding]::UTF8.GetString($data)) like the http listener. Hardening (from PR review): - start() rejects a parent listener that cannot supply staging keys, with a clear message, before any relay/firewall side effects; _staging_cert_key_options guards the same at stage time (time-of-check/time-of-use). - http handle_get serves default_response(404) instead of a bare 500 when generate_stager returns None. - Stageless generation (stager_generation_service, pyinstaller) raise StagerGenerationException instead of crashing on a None building block. Adds regression tests for every fix and guard; each was verified fail-before / pass-after. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…1.0 (#1592) Bumps [MishaKav/pytest-coverage-comment](https://github.com/mishakav/pytest-coverage-comment) from 1.10.0 to 1.11.0. - [Release notes](https://github.com/mishakav/pytest-coverage-comment/releases) - [Changelog](https://github.com/MishaKav/pytest-coverage-comment/blob/main/CHANGELOG.md) - [Commits](MishaKav/pytest-coverage-comment@v1.10.0...v1.11.0) --- updated-dependencies: - dependency-name: MishaKav/pytest-coverage-comment dependency-version: 1.11.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…cript, CWD independence) (#1595)
The 7.0.0 version bump landed on private-main ahead of the release, which left `release-private-start` bumping from 7.0.0 instead of to it (the last dispatch produced a release/8.0.0-private branch). Roll the three files the workflow owns back to their pre-release state: - CHANGELOG.md: fold the `[7.0.0] - 2026-08-01` section back into `[Unreleased]` and point the `[Unreleased]` compare link at v6.7.1 - pyproject.toml: 7.0.0 -> 6.7.1 - empire/server/common/empire.py: VERSION 7.0.0 -> 6.7.1 A `major` dispatch of release-private-start now re-derives all three.
v7.0.0 into private-main
…1591)
* fix(chat): load created_at before expunge so MySQL chat emits work
Every chat message raised DetachedInstanceError on MySQL, killing the
chat/message emit in on_message so senders never saw their own message
appear.
ChatMessage.created_at defaults to utcnow(), a SQL *expression*, so its
value is only known once the INSERT has run. SQLAlchemy returns it inline
via INSERT..RETURNING where the dialect supports it, but MySQL has none
and leaves the attribute unloaded pending a follow-up SELECT.
persist_and_fire_chat expunged the instance before that SELECT could
happen, so msg.created_at.isoformat() had no session to load from.
Load the attribute while the instance is still attached, and only when
the dialect did not already supply it -- free on SQLite/PostgreSQL, one
primary-key SELECT on MySQL.
The suite runs on SQLite, where RETURNING hides this entirely, which is
why it went unnoticed. The regression test turns insert_returning off to
exercise the path MySQL deployments actually take; it fails with the
reported DetachedInstanceError before this change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(chat): address review on created_at preload
Refresh the whole row rather than just created_at (same single PK SELECT,
covers any future expression-default column), and gate on the documented
inspect(msg).unloaded rather than .dict membership.
Correct the docstring's claim that hooks.run_hooks needs the preload: it
dispatches via db.merge(load=False) into a fresh session, which re-attaches
and lazy-loads normally. Only on_message's emit reads the detached object.
Fix the regression test, which false-passed against unfixed code: the ORM
flush compiles INSERTs through the mapper-level cache, so clearing only the
engine's left the RETURNING-flavored INSERT in play and the dialect flip had
no effect when an earlier test in the file had already warmed it.
Correct the test docstring's inverted claim about the default backend (MySQL,
not SQLite), note the --dist=loadfile dependency, and assert isoformat() as
on_message actually evaluates it. Add the CHANGELOG entry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(chat): scope the created_at preload and harden its regression test
Revert the preload to db.refresh(msg, ["created_at"]). A whole-row
db.refresh() expires every attribute including relationships and reloads
only columns, so the db.expunge() on the next line strands any relationship
the caller had already loaded -- measured: msg.user reads back fine after
the scoped refresh, and raises DetachedInstanceError after the whole-row
one. Both forms cost the same single primary-key SELECT, so the wider form
bought nothing and planted the error class this function exists to avoid.
Move the test's dialect mutation and mapper-cache clear inside the try via
monkeypatch. session_local is session-scoped, so an exception before the
restore leaks insert_returning=False across files in the worker: with the
setup clear made to raise, a probe asserting the flag poisoned a different
file's group under CI's own -n 2 --dist=loadfile.
Drop both bind._compiled_cache.clear() calls -- the engine cache never
holds this INSERT (measured: 37 entries, zero chat_messages INSERTs; the
mapper cache holds the one that matters).
Delete the --dist=loadfile safety rationale, which is false. Runs under
--dist=load and --dist=loadfile are byte-identical on both backends; the
flip is safe because xdist workers are separate processes running tests
sequentially, not because of the dist mode.
Assert the emitted INSERT carries no RETURNING, so the flip failing open
fails the test instead of silently passing against unfixed code, and pin
both the refresh scoping and the unloaded-gate. All four mutations are now
caught; two of them were not before.
Correct the CHANGELOG: the emit died room-wide rather than for the sender,
the message was committed and reappeared only on reload, and MariaDB from
10.5 was never affected -- verified against a live MariaDB 10.11, where
unfixed code passes the natural path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(chat): fix assertion ordering and isolation in the created_at test
Assert created_at before the structural checks. With the preload deleted,
the scoping check's next() raised StopIteration first, so the regression
this test exists for surfaced as an unexplained iterator error instead of
DetachedInstanceError. Both next() calls now take a None default and a real
message, so a missing statement says which one.
Strengthen the scoping assertion: it only checked that chat_messages.message
was absent, which passes for any partially-scoped refresh. It now compares
the selected column set against {created_at} and rejects a joined users.
prefix -- the relationship expiry is the harm being pinned.
Scope test_no_refresh's listener to the session's own connection. run_hooks
opens a fresh session on the same engine, so an engine-wide listener would
record SELECTs caused by a registered AFTER_CHAT_MESSAGE_HOOK and blame the
helper for them.
Delete the committed rows in an autouse fixture. persist_and_fire_chat
commits, and these tests were relying on test_basic_reporting_plugin.py's
defensive wipe to keep the shared per-worker database clean.
Correct the stale comment on the dialect mutation: monkeypatch restores at
teardown regardless of where the body fails, so the placement constraint the
comment described no longer exists. getattr-guard the teardown cache clear
so a future SQLAlchemy rename fails on the setup line instead of masking the
real error from inside finally.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(chat): cut the created_at commentary down to what isn't obvious
The explanation had grown to outweigh the two lines of logic it described,
with the same RETURNING/expression-default mechanism spelled out in the
helper docstring, both test docstrings, and the CHANGELOG.
Give each fact one owner: persist_and_fire_chat's docstring carries the
mechanism and the tests point at it. Drop the measurement anecdotes, the
narration of why assertions are ordered as they are, and the paragraph
rebutting a bare attribute touch that ruff rejects anyway.
What stays is the three things a reader cannot recover from the code: the
mapper cache defeats the dialect flip, a whole-row refresh expires
relationships that the expunge then strands, and run_hooks opens its own
session so an engine-wide listener misattributes its SELECTs.
Net 40 lines of prose removed. No behavior change: 3 passed on SQLite and
MariaDB, and deleting the preload, widening the refresh, or dropping the
guard are each still caught.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…609)
* fix: ingest Rubeus dump credentials, add krb_session_key credtype
`Rubeus dump` produced zero credentials. Three independent defects, each
sufficient on its own to empty the result:
1. The ticket anchor only matched `base64(ticket.kirbi):`; `dump` emits
`Base64EncodedTicket :`.
2. `dump` prints its `Key : Value` metadata *above* the ticket body rather
than below, so reading in the kirbi direction attributed each ticket to
the next ticket's fields.
3. The agent transport hard-wraps long lines mid-value
(`UserName : HOST$ (NT_PRINC` / `IPAL)`); the orphan tail read as a block
boundary and truncated the metadata, dropping the username.
The matched anchor now selects the metadata direction, and field lines are
rejoined before parsing. Tickets from `dump` inherit the `UserSID` of the
logon-session header they sit under, and are tagged `rubeus:dump` from the
anchor rather than mislabelled `rubeus:asktgt` by the `Flags` heuristic.
Also adds the `krb_session_key` credtype for the `Base64(key)` session key
printed with every ticket, stored as `<keytype>:<base64key>` because a bare
key is ambiguous between rc4_hmac and the AES variants. `notes` now carry
the service name so a key can be paired with the ticket it decrypts.
Verified against 52KB of real `dump` output from a live agent: 0 -> 34
credentials (17 tickets + 17 session keys), 0 with empty usernames.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(rubeus): address review feedback on dump credential parser
The backward metadata walk's `key not in fields` guard was described as
redundant and was untested: deleting it left all 13 rubeus tests green.
It is not redundant. The docstring claimed the walk stops before reaching
the logon-session header "either way", but that only holds when a blank
line separates the two — the first ticket under a header abuts it, and the
walk carries on into the header's own UserName/Domain. Nearest-key-wins is
the only thing keeping the session owner from shadowing that ticket's
principal. Reworded the docstring and added a header-abutting multi-ticket
fixture that fails with the guard removed.
Session keys for the legacy DES etypes were silently dropped: an 8-byte
key base64s to 12 characters and the `{16,}` floor rejected it, while the
ticket itself landed. `(null)` and empty values are rejected by the base64
charset rather than the floor, so the floor was only ever an etype filter.
Lowered to `{11,}`.
Also: moved `_session_key` to module level alongside the other pure
helpers, softened the `_unwrap_field_lines` comment to state its
mid-token-cut assumption, de-duplicated the layout explanation between the
anchor regex and the class docstring, and tightened two test assertions
(the `rubeus:dump` tag check accepted `rubeus:dump-anything`; the SID
asserts duplicated a dedicated test).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kali <anthony.rose@bc-security.org>
* fix: Inveigh array options fail ValidateSet from quoted comma-joined values
The generic PowerShell command builder always quotes option values
(-{{ KEY }} "{{ VALUE }}"), so Inveigh's [Array] parameters received
their comma-joined defaults as a single element: -NBNSTypes "00,20"
bound as @("00,20"), which trips the parameter's ValidateSet. The
Spoofer host/IP list arrays were silently broken the same way (matched
with -contains, which never matches a single joined element).
Add an optional per-option `format_string` that overrides the
module-wide `advanced.option_format_string` for a single option's value
branch, and use it on Inveigh's six [Array] options to emit them
unquoted (-NBNSTypes 00,20 -> @("00","20")). Scalar options keep the
quoted module default.
Also fix Proxy, which was strict:true with an empty default absent from
its suggested_values, so validation failed on every run that left it
unset; it is optional like HTTPAuth/WPADAuth, so make it strict:false.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016JYsm8bQ9EuqkZq3Xiao6U
* fix: quote each array element for Inveigh [Array] options
The previous fix rendered these options unquoted (-NBNSTypes 00,20), but
PowerShell then parses 00 as the number 0, which fails the string
ValidateSet just as the original single-quoted "00,20" did. Only
per-element quoting works: -NBNSTypes "00","20" -> @("00","20").
Add a {{ VALUE_ARRAY }} format token that expands a comma-separated
value into individually double-quoted, comma-joined elements, and point
Inveigh's six [Array] options at it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016JYsm8bQ9EuqkZq3Xiao6U
* feat: ingest Inveigh NetNTLMv1/v2 captures into the credential store
Invoke-Inveigh captured challenge/response hashes but the module had no
credential_parser, so nothing wrote them to the store. Add an `inveigh`
parser that scans task output for hashcat-5500 (NetNTLMv1) and -5600
(NetNTLMv2) response lines — the two shapes are distinguished by exact
hex-segment lengths, so they never cross-match — and wire it onto the
module YAML. Machine accounts (username ending in `$`, which the module
captures via -MachineAccounts Y) are retained and tagged in notes.
Ingestion runs on every task-result batch, so streamed captures from the
background module are picked up as they arrive.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016JYsm8bQ9EuqkZq3Xiao6U
* fix: make mDNSTypes array support reachable, add changelog and docs
Addresses review feedback on #1610.
- mDNSTypes kept `strict: true` with `suggested_values: [QU, QM]`, so
validate_options rejected "QU,QM" before the {{ VALUE_ARRAY }} format
string could render — its array support was unreachable for every input
that needed it. Now non-strict, keeping the values as UI suggestions
(Starkiller renders a combobox rather than a locked dropdown when strict
is false). No other option in the repo pairs VALUE_ARRAY with strict.
- {{ VALUE_ARRAY }} now strips each element and drops blanks, so a list
typed as "00, 20," binds as @("00","20") instead of emitting " 20" and
an empty element that fail the same ValidateSet this fix exists to
satisfy. It is also only computed when the format string references it.
- CHANGELOG [Unreleased] entries for the Inveigh credential ingestion and
for the new per-option `format_string` / {{ VALUE_ARRAY }} token.
- docs: credential-parser count eleven->twelve and sixteen->seventeen
modules with `inveigh` listed; new format_string / {{ VALUE_ARRAY }}
section in the PowerShell module docs covering both strict pitfalls
(a strict array option is unreachable; strict with no suggested_values
rejects every value).
- Tests: pin the dedup key against a same-user re-authentication — keying
`seen` on the username rather than the whole line previously passed the
entire suite; cover all five length-constrained regex segments plus a
character-class relaxation and the console noise lines; render all six
array options, four of which shipped untested; pin element strip and
blank-drop.
- Comment accuracy: the parser dedups within a batch, not absolutely
(cross-batch repeats are caught by the credential store); a file option
reaching the render loop would crash at value.lower() rather than
silently miss its override.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Kali <anthony.rose@bc-security.org>
The credential parser subsystem had no developer documentation — the only coverage was the operator-facing Starkiller credentials page, which described what parsers do but not how to write one. Add a Credential Parsers page under Modules -> Module Development covering the ingestion flow, the CredentialParser protocol, the agent=None contract for non-agent ingestion paths, credtype selection, registry and YAML wiring, the error handling ingestion provides, and the inline-fixture testing convention. Also refresh the stale parser inventory on the Starkiller page: the inveigh parser added in 519791f0d brought the registry to twelve parsers and seventeen declaring modules, and cross-link the new page. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix: don't log the staging key in plaintext get_staging_key() logged the configured key's value at INFO level when one was supplied via STAGING_KEY or config.yaml. The staging key keys the AES-GCM routing packet layer and the AES-CBC+HMAC staging envelope, so it is a long-lived secret and does not belong in logs, which get shipped to aggregators and captured in support bundles. The line was also redundant: "Using preset staging key" is already logged a few lines above, so this just drops it. The randomly generated path never logged the value. Closes #1615 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: don't render the staging key in Config.__repr__ A repr can reach logs, tracebacks, or an interactive dump, so embedding the staging key value there is the same exposure #1615 closes for the INFO log line. Show only whether the key is set. Refs #1615 * docs(changelog): note staging key removed from logs and repr --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix: require agent certificate verification during staging The PowerShell and C# staging branches guarded the Ed25519 check with `if any(agent_cert)`, which is False for an all-zero buffer. Sending 64 zero bytes therefore skipped verification entirely and staging carried on, registering the agent. The Python and Go branches already verified unconditionally; this brings the other two in line. Impact is limited on its own -- reaching this code needs a valid staging-key HMAC, and the agent signing key ships in every stager, so an attacker here could equally produce a real signature. It matters as a prerequisite for binding handshake signatures to the transcript (#1612), which this branch would otherwise let a caller downgrade straight back out of. test__handle_agent_staging was an empty stub, so this path had no coverage at all. Replaced it with tests for a zeroed cert, a signature from the wrong key, and the valid happy path, each parametrized over powershell and csharp. Verified the zeroed-cert tests fail without the source change. Closes #1614 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(changelog): note mandatory agent-cert verification --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The 7.0.1 entries ran ~2-2.5x the 7.0.0 median (median 60w, max 129w vs 7.0.0's 26w), each leading with a claim then 1-3 sentences of mechanism. Trimmed every entry to a single sentence at 7.0.0 altitude (median 21w, max 24w), keeping module names, credtypes, and identifiers; mechanism and rationale stay in the PRs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a Changelog subsection under Submitting Code: add an entry under the [Unreleased] section, and keep it to one concise sentence (~25 words max), leaving mechanism and rationale to the PR. Anchors length to a number rather than "match the surrounding entries", which ratchets up over time. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v7.0.1 into private-main
* chore: bump empire_compiler ref to v2.0.1 Updates the pinned Empire-Compiler release in both the runtime and test server configs. v2.0.1 bumps the Sharpire and Certify submodules and repoints Certify at the BC-SECURITY fork. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014GemSMYkJEhL7JgtWEaaYK * fix: update Certify module to 2.0 command format (#1627) * fix: update Certify module to 2.0 command format Update the Certify YAML module definition for Certify 2.0 compatibility: - Switch to Net47 and MergeReferences - Simplify the wrapper code to pass args directly - Add 2.0 command suggestions (enum-cas, enum-templates, request, etc.) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Aaw4jYBfPgieNZC4aEcw2 * fix: wrap description line to satisfy yamlfmt max_line_length Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Hubbl3 <jake.krasnov@bcsecurity.io> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Jacob Krasnov <jacob.krasnov@gmail.com> (cherry picked from commit e341672c4097f9c040f23771f4a9a721b9f6b5b5) * docs: add changelog entry for the Certify 2.0 module update The cherry-picked #1627 landed without one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qtqz37SY7ToKbGGxdWNKx2 * fix: persist cd directory changes in Gopire agent (#1626) RunCommand spawns a new process per call, so cd inside a shell subprocess has no effect on the agent's working directory. Intercept cd commands in case 40 (TASK_SHELL) and route them to os.Chdir so the change persists across subsequent commands. Also add a case 44 (TASK_CHDIR) handler that was previously missing. Claude-Session: https://claude.ai/code/session_011Aaw4jYBfPgieNZC4aEcw2 Co-authored-by: Hubbl3 <jake.krasnov@bcsecurity.io> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> (cherry picked from commit 9707a11639c28e989f92b70b877b42dac677de36) * docs: add changelog entry for the Gopire cd fix The cherry-picked #1626 landed without one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qtqz37SY7ToKbGGxdWNKx2 * chore: bump empire_compiler ref to v2.0.2 v2.0.2 adds BouncyCastle.Crypto.dll and updates CommandLine.dll and System.Net.Http.dll required by Certify 2.0, and switches the Certify submodule to the BC-SECURITY fork with null-safety fixes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Hubble <42596432+Hubbl3@users.noreply.github.com> Co-authored-by: Hubbl3 <jake.krasnov@bcsecurity.io> Co-authored-by: Jacob Krasnov <jacob.krasnov@gmail.com>
Debian 11 reached end of security support on 2026-08-31. The final bullseye-security release file carried Valid-Until: 2026-09-07 21:13 UTC and has now expired, so `apt-get update` exits 100 on any debian:bullseye base. That breaks the CI install-test image build before install.sh even runs, and it breaks real installs on Debian 11 the same way. - Remove debian11 from the install-test matrix, the compose file, and run-all-cst.sh - Narrow the installer's Debian detection to 12/13 and explain the EOL rather than printing a bare "Unsupported OS" (--override-os still works) - Update the supported-OS lists in README.md and the install docs Claude-Session: https://claude.ai/code/session_01QCeWjoLEpXeZM1jFjReeg3 Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: GitHub Actions <noreply@github.com>
Resolves the config.yaml conflict: - starkiller.ref stays v4.0.3 (main still had the 6.x pin v3.6.0) - plugin_marketplace points at the public Empire-Plugin-Registry over https, on the 7.x branch — the sponsors tree pins the sponsors registry over ssh, which public users cannot clone - drops the submodules block; 7.x removed SubmodulesConfig and only keeps a tolerance test for the stale key Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QCeWjoLEpXeZM1jFjReeg3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was automatically generated by the
release-public-startworkflow.This PR should be merged with a merge commit, not a squash commit.
Merging this PR will trigger a tag and release automatically.