harden(opsec): disguise honeypot action labels in the audit log
The audit log (readable by every role) rendered "Honeypot-Treffer" / "Honeypot hit" next to the decoy paths, which advertises the deception layer to anyone with panel/audit access — an attacker seeing it would know wp-admin/phpmyadmin/etc. are traps. Rename the VISIBLE labels to neutral WAF/IDS-style threat logging so the mechanism is no longer self-revealing. The DB action keys stay security.honeypot_* (Threats intel, history, error styling, and tests key off them); only the human-facing text changes: security.honeypot_hit -> "Angriffsversuch" / "Attack attempt" security.honeypot_login -> "Login-Angriffsversuch" / "Login attack attempt" security.honeytoken_used -> "Kompromittierte Zugangsdaten" / "Compromised credentials" This covers the /audit page, the dashboard audit panel, and the Threats feed rows (all resolve the same audit.actions label). A regression test asserts the audit log renders the disguised labels and never leaks "Honeypot"/"Honeytoken" (nor the raw key). 618 tests, de/en parity, Pint. NOTE: the admin-only Threats page header (subtitle/feed title/Honeytoken KPI) and the settings honeypot-status section still name it "Honeypot" — those are manage-panel-gated; disguising them too is a separate, optional follow-up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>feat/v1-foundation
parent
930f78efbd
commit
bf4366dfc9
|
|
@ -60,9 +60,12 @@ return [
|
||||||
'password.change' => 'Passwort geändert',
|
'password.change' => 'Passwort geändert',
|
||||||
'password.reset' => 'Passwort zurückgesetzt',
|
'password.reset' => 'Passwort zurückgesetzt',
|
||||||
'profile.update' => 'Profil aktualisiert',
|
'profile.update' => 'Profil aktualisiert',
|
||||||
'security.honeypot_hit' => 'Honeypot-Treffer',
|
// Neutral, WAF/IDS-style labels — deliberately do NOT reveal the deception layer to anyone
|
||||||
'security.honeypot_login' => 'Honeypot-Login-Versuch',
|
// reading the audit log. The DB action keys stay `security.honeypot_*`; only the visible text
|
||||||
'security.honeytoken_used' => 'Honeytoken benutzt',
|
// is disguised so an attacker with panel/audit access cannot tell the decoy paths are a trap.
|
||||||
|
'security.honeypot_hit' => 'Angriffsversuch',
|
||||||
|
'security.honeypot_login' => 'Login-Angriffsversuch',
|
||||||
|
'security.honeytoken_used' => 'Kompromittierte Zugangsdaten',
|
||||||
'server.create' => 'Server angelegt',
|
'server.create' => 'Server angelegt',
|
||||||
'server.credential_updated' => 'SSH-Zugangsdaten aktualisiert',
|
'server.credential_updated' => 'SSH-Zugangsdaten aktualisiert',
|
||||||
'ssh_key.add' => 'SSH-Schlüssel hinzugefügt',
|
'ssh_key.add' => 'SSH-Schlüssel hinzugefügt',
|
||||||
|
|
|
||||||
|
|
@ -60,9 +60,12 @@ return [
|
||||||
'password.change' => 'Password changed',
|
'password.change' => 'Password changed',
|
||||||
'password.reset' => 'Password reset',
|
'password.reset' => 'Password reset',
|
||||||
'profile.update' => 'Profile updated',
|
'profile.update' => 'Profile updated',
|
||||||
'security.honeypot_hit' => 'Honeypot hit',
|
// Neutral, WAF/IDS-style labels — deliberately do NOT reveal the deception layer to anyone
|
||||||
'security.honeypot_login' => 'Honeypot login attempt',
|
// reading the audit log. The DB action keys stay `security.honeypot_*`; only the visible text
|
||||||
'security.honeytoken_used' => 'Honeytoken used',
|
// is disguised so an attacker with panel/audit access cannot tell the decoy paths are a trap.
|
||||||
|
'security.honeypot_hit' => 'Attack attempt',
|
||||||
|
'security.honeypot_login' => 'Login attack attempt',
|
||||||
|
'security.honeytoken_used' => 'Compromised credentials',
|
||||||
'server.create' => 'Server added',
|
'server.create' => 'Server added',
|
||||||
'server.credential_updated' => 'SSH credentials updated',
|
'server.credential_updated' => 'SSH credentials updated',
|
||||||
'ssh_key.add' => 'SSH key added',
|
'ssh_key.add' => 'SSH key added',
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,24 @@ class AuditLogDisplayTest extends TestCase
|
||||||
->assertDontSee('wg.set-endpoint');
|
->assertDontSee('wg.set-endpoint');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_honeypot_events_render_disguised_and_never_leak_the_deception(): void
|
||||||
|
{
|
||||||
|
// OPSEC: the audit log is readable by every role, so the honeypot/deception action labels must
|
||||||
|
// NOT reveal that the decoy paths are a trap. The DB action keys stay security.honeypot_* (for
|
||||||
|
// the Threats intel + history), but the VISIBLE label reads like generic WAF/IDS threat logging.
|
||||||
|
AuditEvent::create(['actor' => 'system', 'action' => 'security.honeypot_hit', 'target' => '/wp-admin', 'ip' => '203.0.113.9']);
|
||||||
|
AuditEvent::create(['actor' => 'system', 'action' => 'security.honeypot_login', 'target' => '/wp-login.php', 'ip' => '203.0.113.9']);
|
||||||
|
AuditEvent::create(['actor' => 'system', 'action' => 'security.honeytoken_used', 'target' => 'db_password', 'ip' => '203.0.113.9']);
|
||||||
|
|
||||||
|
Livewire::test(Index::class)
|
||||||
|
->assertSee('Angriffsversuch') // honeypot_hit → disguised
|
||||||
|
->assertSee('Login-Angriffsversuch') // honeypot_login → disguised
|
||||||
|
->assertSee('Kompromittierte Zugangsdaten') // honeytoken_used → disguised
|
||||||
|
->assertDontSee('Honeypot') // the word never reaches the audit log
|
||||||
|
->assertDontSee('Honeytoken')
|
||||||
|
->assertDontSee('honeypot'); // nor the raw action key
|
||||||
|
}
|
||||||
|
|
||||||
public function test_search_matches_the_readable_label(): void
|
public function test_search_matches_the_readable_label(): void
|
||||||
{
|
{
|
||||||
AuditEvent::create(['actor' => 'Administrator', 'action' => 'wg.set-endpoint', 'target' => 'x', 'ip' => '127.0.0.1']);
|
AuditEvent::create(['actor' => 'Administrator', 'action' => 'wg.set-endpoint', 'target' => 'x', 'ip' => '127.0.0.1']);
|
||||||
|
|
|
||||||
|
|
@ -102,8 +102,8 @@ class ThreatsPageTest extends TestCase
|
||||||
->test(Index::class)
|
->test(Index::class)
|
||||||
->assertSee(self::ATTACKER_IP)
|
->assertSee(self::ATTACKER_IP)
|
||||||
->assertSee('/wp-login.php')
|
->assertSee('/wp-login.php')
|
||||||
->assertSee('Honeypot-Treffer') // localized action label (DE default)
|
->assertSee('Angriffsversuch') // disguised action label (DE default) — no "Honeypot" leak
|
||||||
->assertSee('Honeytoken benutzt')
|
->assertSee('Kompromittierte Zugangsdaten')
|
||||||
->assertViewHas('probes_24h', 2) // 2 recent hits, the 3-day-old one excluded
|
->assertViewHas('probes_24h', 2) // 2 recent hits, the 3-day-old one excluded
|
||||||
->assertViewHas('banned_total', 1)
|
->assertViewHas('banned_total', 1)
|
||||||
->assertViewHas('honeytoken_trips', 1)
|
->assertViewHas('honeytoken_trips', 1)
|
||||||
|
|
@ -123,7 +123,7 @@ class ThreatsPageTest extends TestCase
|
||||||
Livewire::actingAs($this->admin())
|
Livewire::actingAs($this->admin())
|
||||||
->test(Index::class)
|
->test(Index::class)
|
||||||
->assertSee(self::ATTACKER_IP)
|
->assertSee(self::ATTACKER_IP)
|
||||||
->assertSee('Honeypot-Login-Versuch') // localized action label (DE default)
|
->assertSee('Login-Angriffsversuch') // disguised action label (DE default) — no "Honeypot" leak
|
||||||
->assertSee('admin') // captured tried_user
|
->assertSee('admin') // captured tried_user
|
||||||
->assertSee('hunter2') // captured tried_pass
|
->assertSee('hunter2') // captured tried_pass
|
||||||
->assertViewHas('login_attempts_24h', 1);
|
->assertViewHas('login_attempts_24h', 1);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue