Skip to content

Commit 2e2c80a

Browse files
Jonathan D.A. Jewellclaude
andcommitted
feat: v1.0 shipping preparation — signature scheme flag, SQLi tests, deployment package
Feature-flag ed448: add SignatureScheme enum (MlDsa87Only default, Hybrid optional). ML-DSA-87 alone is FIPS 204 peer-reviewed and production-safe. Ed448 (unaudited) can be enabled per-config when audit completes. Add 6 database proxy SQLi smoke tests proving: legitimate SELECTs pass, mutable table writes allowed, immutable table writes blocked, DROP/ALTER always blocked, stacked query injection caught by AST parser. Add deployment package: setup.sh (creates dirs, pulls images, downloads WordPress, generates wp-config.php routing DB through yacht-agent proxy), DEPLOY.adoc with step-by-step guide. Add hack-me security challenge document (docs/HACK-ME-CHALLENGE.adoc) for public bug bounty when a live site is deployed. Add WordPress adapter plugin (adapters/wordpress-wharf/, GPL-2.0) — dashboard widget showing agent stats, admin bar indicator, wordpress.org readme.txt. Pure display, zero security logic in PHP. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d3308b9 commit 2e2c80a

10 files changed

Lines changed: 908 additions & 16 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
=== Wharf Security Adapter ===
2+
Contributors: hyperpolymath
3+
Tags: security, firewall, database-proxy, post-quantum
4+
Requires at least: 6.0
5+
Tested up to: 6.7
6+
Stable tag: 1.0.0
7+
Requires PHP: 8.0
8+
License: GPLv2 or later
9+
License URI: https://www.gnu.org/licenses/gpl-2.0.html
10+
11+
Dashboard widget and status display for sites protected by Project Wharf.
12+
13+
== Description ==
14+
15+
Project Wharf is a Sovereign Web Hypervisor — an external security layer that sits
16+
between the internet and your WordPress site. This adapter plugin provides a
17+
dashboard widget showing the protection status.
18+
19+
**This plugin does NO security work itself.** All security enforcement is handled by
20+
the yacht-agent, a separate Rust daemon that:
21+
22+
* Proxies all database queries through an AST-aware SQL parser
23+
* Blocks SQL injection at the wire protocol level (not regex)
24+
* Monitors file integrity via BLAKE3 cryptographic hashes
25+
* Enforces write policies (immutable tables, no DROP/ALTER)
26+
* Uses ML-DSA-87 post-quantum signatures for the control plane
27+
28+
= What This Plugin Does =
29+
30+
* Adds a "Wharf Security Status" widget to your WordPress dashboard
31+
* Shows query statistics (total, blocked, audited)
32+
* Displays firewall mode and signature scheme
33+
* Adds an admin bar indicator (green = protected, grey = agent unreachable)
34+
* Caches agent responses for 30 seconds to minimise overhead
35+
36+
= What This Plugin Does NOT Do =
37+
38+
* Does not implement any security features (that's the yacht-agent's job)
39+
* Does not modify your database or files
40+
* Does not phone home or collect telemetry
41+
* Does not require an account or subscription
42+
43+
== Installation ==
44+
45+
1. Install and configure Project Wharf on your server (see https://github.com/hyperpolymath/project-wharf)
46+
2. Upload the `wharf-adapter` folder to `/wp-content/plugins/`
47+
3. Activate the plugin through the 'Plugins' menu
48+
4. Visit your Dashboard to see the Wharf Security Status widget
49+
50+
= Configuration =
51+
52+
Set the `WHARF_AGENT_URL` environment variable if your yacht-agent runs on a
53+
non-default address:
54+
55+
define('WHARF_AGENT_URL', 'http://agent:9001');
56+
57+
Default is `http://localhost:9001`.
58+
59+
== Changelog ==
60+
61+
= 1.0.0 =
62+
* Initial release
63+
* Dashboard widget with query stats
64+
* Admin bar status indicator
65+
* Agent health monitoring
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
/**
3+
* Plugin Name: Wharf Security Adapter
4+
* Plugin URI: https://github.com/hyperpolymath/project-wharf
5+
* Description: Dashboard widget and status display for sites protected by Project Wharf (yacht-agent).
6+
* Version: 1.0.0
7+
* Author: Jonathan D.A. Jewell
8+
* Author URI: https://github.com/hyperpolymath
9+
* License: GPL-2.0-or-later
10+
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
11+
* Text Domain: wharf-adapter
12+
*
13+
* SPDX-License-Identifier: GPL-2.0-or-later
14+
*
15+
* This plugin is a thin adapter — it does NO security work itself.
16+
* All security enforcement is handled by the yacht-agent (a separate Rust daemon).
17+
* This plugin simply reads status from the agent's API and displays it in wp-admin.
18+
*/
19+
20+
if (!defined('ABSPATH')) {
21+
exit;
22+
}
23+
24+
define('WHARF_ADAPTER_VERSION', '1.0.0');
25+
define('WHARF_AGENT_URL', getenv('WHARF_AGENT_URL') ?: 'http://localhost:9001');
26+
27+
/**
28+
* Register the dashboard widget
29+
*/
30+
function wharf_add_dashboard_widget() {
31+
wp_add_dashboard_widget(
32+
'wharf_security_status',
33+
'Wharf Security Status',
34+
'wharf_render_dashboard_widget'
35+
);
36+
}
37+
add_action('wp_dashboard_setup', 'wharf_add_dashboard_widget');
38+
39+
/**
40+
* Fetch stats from yacht-agent API
41+
*/
42+
function wharf_fetch_agent_stats() {
43+
$cache_key = 'wharf_agent_stats';
44+
$cached = get_transient($cache_key);
45+
if ($cached !== false) {
46+
return $cached;
47+
}
48+
49+
$response = wp_remote_get(WHARF_AGENT_URL . '/stats', array(
50+
'timeout' => 3,
51+
'sslverify' => false,
52+
));
53+
54+
if (is_wp_error($response)) {
55+
return array('error' => $response->get_error_message());
56+
}
57+
58+
$body = wp_remote_retrieve_body($response);
59+
$data = json_decode($body, true);
60+
61+
if (!is_array($data)) {
62+
return array('error' => 'Invalid response from yacht-agent');
63+
}
64+
65+
set_transient($cache_key, $data, 30); // Cache for 30 seconds
66+
return $data;
67+
}
68+
69+
/**
70+
* Render the dashboard widget
71+
*/
72+
function wharf_render_dashboard_widget() {
73+
$stats = wharf_fetch_agent_stats();
74+
75+
if (isset($stats['error'])) {
76+
echo '<p style="color:#d63638"><strong>Agent Unreachable:</strong> ';
77+
echo esc_html($stats['error']);
78+
echo '</p>';
79+
echo '<p>Ensure yacht-agent is running at <code>' . esc_html(WHARF_AGENT_URL) . '</code></p>';
80+
return;
81+
}
82+
83+
$queries_allowed = isset($stats['queries_allowed']) ? intval($stats['queries_allowed']) : 0;
84+
$queries_blocked = isset($stats['queries_blocked']) ? intval($stats['queries_blocked']) : 0;
85+
$moored = !empty($stats['moored']);
86+
$firewall = isset($stats['firewall_mode']) ? esc_html($stats['firewall_mode']) : 'unknown';
87+
$sig_scheme = isset($stats['signature_scheme']) ? esc_html($stats['signature_scheme']) : 'unknown';
88+
89+
echo '<table class="widefat" style="border:0">';
90+
91+
// Status indicator
92+
$status_color = $moored ? '#00a32a' : '#dba617';
93+
$status_label = $moored ? 'Moored (Connected)' : 'Unmoored (Standalone)';
94+
echo '<tr><td><strong>Status</strong></td>';
95+
echo '<td><span style="color:' . $status_color . '">&#9679;</span> ' . $status_label . '</td></tr>';
96+
97+
// Query stats
98+
$total = $queries_allowed + $queries_blocked;
99+
echo '<tr><td><strong>DB Queries</strong></td>';
100+
echo '<td>' . number_format($total) . ' total (' . number_format($queries_blocked) . ' blocked)</td></tr>';
101+
102+
// Firewall
103+
echo '<tr><td><strong>Firewall</strong></td>';
104+
echo '<td>' . $firewall . '</td></tr>';
105+
106+
// Signature scheme
107+
echo '<tr><td><strong>Signatures</strong></td>';
108+
echo '<td>' . $sig_scheme . '</td></tr>';
109+
110+
echo '</table>';
111+
112+
echo '<p style="margin-top:12px;color:#757575;font-size:12px">';
113+
echo 'Protected by <a href="https://github.com/hyperpolymath/project-wharf" target="_blank">Project Wharf</a>';
114+
echo ' &mdash; The Sovereign Web Hypervisor';
115+
echo '</p>';
116+
}
117+
118+
/**
119+
* Add admin bar indicator
120+
*/
121+
function wharf_admin_bar_item($admin_bar) {
122+
$stats = wharf_fetch_agent_stats();
123+
$is_healthy = !isset($stats['error']);
124+
125+
$admin_bar->add_node(array(
126+
'id' => 'wharf-status',
127+
'title' => ($is_healthy ? '&#9679; ' : '&#9675; ') . 'Wharf',
128+
'href' => admin_url(),
129+
'meta' => array(
130+
'title' => $is_healthy ? 'Wharf: Protected' : 'Wharf: Agent unreachable',
131+
),
132+
));
133+
}
134+
add_action('admin_bar_menu', 'wharf_admin_bar_item', 100);

bin/yacht-agent/src/main.rs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use tracing_subscriber::FmtSubscriber;
3434
use wharf_core::config::{ConfigLoader, YachtAgentConfig};
3535
use wharf_core::crypto::{
3636
self, HybridKeypair, HybridPublicKey, generate_hybrid_keypair, hybrid_public_key,
37-
verify_hybrid, serialize_public_key,
37+
verify_with_scheme, serialize_public_key, SignatureScheme,
3838
serialize_keypair_raw, deserialize_keypair_raw,
3939
};
4040
use wharf_core::db_policy::{DatabasePolicy, PolicyEngine, QueryAction};
@@ -137,6 +137,10 @@ struct Args {
137137
#[arg(long, default_value_t = true, env = "METRICS_ENABLED")]
138138
metrics_enabled: bool,
139139

140+
/// Signature scheme: ml-dsa-87-only (default, production-safe) or hybrid (requires ed448 audit)
141+
#[arg(long, default_value = "ml-dsa-87-only", env = "SIGNATURE_SCHEME")]
142+
signature_scheme: String,
143+
140144
/// Enable verbose logging
141145
#[arg(short, long, action = clap::ArgAction::Count)]
142146
verbose: u8,
@@ -179,6 +183,9 @@ struct AgentState {
179183
/// Site root for integrity verification
180184
site_root: Option<String>,
181185

186+
/// Signature scheme for mooring verification
187+
signature_scheme: SignatureScheme,
188+
182189
/// Statistics
183190
queries_allowed: u64,
184191
queries_blocked: u64,
@@ -188,7 +195,7 @@ struct AgentState {
188195
}
189196

190197
impl AgentState {
191-
fn new(key_store_dir: Option<&str>) -> Self {
198+
fn new(key_store_dir: Option<&str>, signature_scheme: SignatureScheme) -> Self {
192199
// Load or generate yacht keypair with persistence
193200
let (keypair, pubkey) = Self::load_or_generate_keypair(key_store_dir);
194201

@@ -203,6 +210,7 @@ impl AgentState {
203210
yacht_pubkey: pubkey,
204211
last_mooring_time: None,
205212
site_root: None,
213+
signature_scheme,
206214
queries_allowed: 0,
207215
queries_blocked: 0,
208216
queries_audited: 0,
@@ -453,8 +461,15 @@ async fn main() -> anyhow::Result<()> {
453461
error!("WARNING: No firewall protection active!");
454462
}
455463

464+
// Parse signature scheme from CLI arg
465+
let signature_scheme = match args.signature_scheme.as_str() {
466+
"hybrid" => SignatureScheme::Hybrid,
467+
_ => SignatureScheme::MlDsa87Only,
468+
};
469+
info!("Signature scheme: {:?}", signature_scheme);
470+
456471
// Initialize shared state with persistent keypair
457-
let mut agent_state = AgentState::new(config.key_store_dir.as_deref());
472+
let mut agent_state = AgentState::new(config.key_store_dir.as_deref(), signature_scheme);
458473
agent_state.site_root = config.site_root.clone();
459474
let state = Arc::new(RwLock::new(agent_state));
460475

@@ -793,8 +808,8 @@ async fn mooring_init(
793808
Ok(wharf_pk) => {
794809
match crypto::deserialize_signature(&request.signature) {
795810
Ok(sig) => {
796-
if let Err(e) = verify_hybrid(&wharf_pk, &canonical, &sig) {
797-
warn!("Hybrid signature verification failed: {}", e);
811+
if let Err(e) = verify_with_scheme(&wharf_pk, &canonical, &sig, state_guard.signature_scheme) {
812+
warn!("Signature verification failed: {}", e);
798813
return Json(serde_json::json!({
799814
"error": "invalid_signature",
800815
"message": format!("Signature verification failed: {}", e)
@@ -1326,7 +1341,7 @@ mod tests {
13261341
#[tokio::test]
13271342
async fn test_mooring_e2e_flow() {
13281343
// Build the yacht-agent API on a random port
1329-
let state = Arc::new(RwLock::new(AgentState::new(None)));
1344+
let state = Arc::new(RwLock::new(AgentState::new(None, SignatureScheme::default())));
13301345
let app = build_api_router(state.clone(), true);
13311346

13321347
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
@@ -1395,7 +1410,7 @@ mod tests {
13951410
/// Test that the health endpoint responds
13961411
#[tokio::test]
13971412
async fn test_health_endpoint() {
1398-
let state = Arc::new(RwLock::new(AgentState::new(None)));
1413+
let state = Arc::new(RwLock::new(AgentState::new(None, SignatureScheme::default())));
13991414
let app = build_api_router(state, false);
14001415

14011416
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
@@ -1420,7 +1435,7 @@ mod tests {
14201435
/// Test that metrics endpoint returns real counters
14211436
#[tokio::test]
14221437
async fn test_metrics_endpoint() {
1423-
let state = Arc::new(RwLock::new(AgentState::new(None)));
1438+
let state = Arc::new(RwLock::new(AgentState::new(None, SignatureScheme::default())));
14241439
{
14251440
let mut s = state.write().await;
14261441
s.queries_allowed = 42;
@@ -1451,7 +1466,7 @@ mod tests {
14511466
/// Test that stats endpoint returns real counters
14521467
#[tokio::test]
14531468
async fn test_stats_endpoint() {
1454-
let state = Arc::new(RwLock::new(AgentState::new(None)));
1469+
let state = Arc::new(RwLock::new(AgentState::new(None, SignatureScheme::default())));
14551470
{
14561471
let mut s = state.write().await;
14571472
s.queries_allowed = 10;

crates/wharf-core/src/config.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
//! 5. System config (`/etc/wharf/`)
1717
//! 6. Hardcoded defaults
1818
19+
use crate::crypto::SignatureScheme;
1920
use serde::{Deserialize, Serialize};
2021
use std::path::{Path, PathBuf};
2122
use thiserror::Error;
@@ -67,6 +68,9 @@ pub struct YachtAgentConfig {
6768

6869
/// Directory for storing persistent keypairs (default: /etc/wharf/keys/)
6970
pub key_store_dir: Option<String>,
71+
72+
/// Signature scheme: "ml-dsa-87-only" (default, production-safe) or "hybrid" (requires ed448 audit)
73+
pub signature_scheme: SignatureScheme,
7074
}
7175

7276
/// Logging configuration
@@ -198,6 +202,9 @@ pub struct WharfCliConfig {
198202

199203
/// Directory for storing persistent keypairs (default: ~/.wharf/keys/)
200204
pub key_store_dir: Option<String>,
205+
206+
/// Signature scheme: "ml-dsa-87-only" (default, production-safe) or "hybrid" (requires ed448 audit)
207+
pub signature_scheme: SignatureScheme,
201208
}
202209

203210
/// Path configuration

0 commit comments

Comments
 (0)