diff --git a/CHANGELOG.md b/CHANGELOG.md
index b5d62f0..2b5b81a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [0.12.0] - 2026-02-04
+
+### Security
+
+- Completed comprehensive security audit (Phase 12)
+- Verified WordPress best practices compliance across entire codebase
+- Confirmed protection against SQL Injection: all database queries use `$wpdb->prepare()` or WP_Query
+- Confirmed protection against XSS: all output properly escaped with `esc_html()`, `esc_attr()`, `esc_url()`
+- Confirmed protection against CSRF: nonce verification on all forms and admin AJAX handlers
+- Verified REST API endpoint security: proper permission callbacks, rate limiting, input sanitization
+- Sensitive data (ID/passport numbers) properly encrypted and not exposed via API
+
+### Fixed
+
+- Fixed Calculator being called statically in API controllers (`BookingsController`, `RoomsController`, `PricingController`)
+- Fixed EmailNotifier method names in BookingsController (`send_admin_new_booking`, `send_cancellation`, `send_guest_confirmation`)
+- Fixed guest_id type casting in EmailNotifier (string to int from post meta)
+
+### Notes
+
+- Public AJAX endpoints (search, availability, calendar, price calculation) intentionally do not require nonce verification as they are read-only public APIs with proper input sanitization
+- All admin AJAX endpoints properly protected with nonce verification and capability checks
+
## [0.11.3] - 2026-02-03
### Changed
diff --git a/CLAUDE.md b/CLAUDE.md
index cf6f629..1ce2205 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1268,3 +1268,70 @@ Admin features always work; frontend requires valid license.
- v0.11.2: Calendar room column width and building name display
- v0.11.3: Calendar filters side-by-side layout
- Both versions tagged and pushed to origin
+
+### 2026-02-04 - Version 0.12.0 (Security Audit)
+
+**Completed:**
+
+- Comprehensive security audit (Phase 12)
+- WordPress best practices compliance verification
+- OWASP Top 10 vulnerability review
+- Live API endpoint testing against localhost:9080
+
+**Security Audit Findings:**
+
+1. **SQL Injection:** ✓ PROTECTED
+ - All `$wpdb` queries use `$wpdb->prepare()` with parameterized queries
+ - WP_Query used throughout for post queries (inherently safe)
+ - Format specifiers (`%s`, `%d`) properly used in `$wpdb->update()` calls
+
+2. **XSS (Cross-Site Scripting):** ✓ PROTECTED
+ - PHP output consistently uses `esc_html()`, `esc_attr()`, `esc_url()`
+ - JavaScript uses `escapeHtml()` function (textContent/innerHTML pattern)
+ - Form values properly escaped before output
+
+3. **CSRF (Cross-Site Request Forgery):** ✓ PROTECTED
+ - All forms use `wp_nonce_field()`
+ - Admin AJAX handlers use `check_ajax_referer()` or `wp_verify_nonce()`
+ - Capability checks with `current_user_can()` on privileged operations
+ - Public AJAX endpoints are read-only and don't modify data
+
+4. **REST API Security:** ✓ PROTECTED
+ - Permission callbacks on all admin endpoints (`admin_permission`)
+ - Rate limiting via transient-based `RateLimiter` class
+ - Input sanitization via `sanitize_callback` on all parameters
+ - Sensitive data (ID/passport) not exposed via API
+
+5. **Data Encryption:** ✓ IMPLEMENTED
+ - Guest ID/passport numbers encrypted with AES-256-CBC
+ - IV stored with encrypted data for secure decryption
+
+**Bugs Fixed During Audit:**
+
+- Fixed `Calculator::calculate()` being called statically (non-static method)
+ - `src/Api/Controllers/BookingsController.php`
+ - `src/Api/Controllers/RoomsController.php`
+ - `src/Api/Controllers/PricingController.php`
+- Fixed incorrect EmailNotifier method names in BookingsController
+ - `send_admin_notification` → `send_admin_new_booking`
+ - `send_cancellation_email` → `send_cancellation`
+ - `send_confirmation_email` → `send_guest_confirmation`
+- Fixed guest_id type casting in EmailNotifier (string from post meta → int)
+
+**Files Changed:**
+
+- `src/Api/Controllers/BookingsController.php` - Calculator instantiation, EmailNotifier method names
+- `src/Api/Controllers/RoomsController.php` - Calculator instantiation
+- `src/Api/Controllers/PricingController.php` - Calculator instantiation
+- `src/Booking/EmailNotifier.php` - guest_id type casting
+- `wp-bnb.php` - Version bump to 0.12.0
+- `CHANGELOG.md` - Added v0.12.0 security audit notes
+- `PLAN.md` - Marked Phase 12 complete
+
+**Learnings:**
+
+- `get_post_meta()` always returns strings; cast to `(int)` when needed for type-hinted methods
+- Static vs instance method calls must match the method declaration
+- Public frontend AJAX endpoints can safely skip nonce verification if they're read-only
+- Rate limiting headers (`X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`) provide client feedback
+- WordPress REST API permission callbacks should use capability checks, not user login status alone
diff --git a/PLAN.md b/PLAN.md
index 07424dd..767e3ae 100644
--- a/PLAN.md
+++ b/PLAN.md
@@ -211,11 +211,12 @@ This document outlines the implementation plan for the WP BnB Management plugin.
- [x] Order management
- [x] Refund handling
-## Phase 12: Security Audit (v0.12.0)
+## Phase 12: Security Audit (v0.12.0) - Complete
-- [ ] Check for Wordpress best-practices
-- [ ] Review the code for OWASP Top 10, including XSS, XSRF, SQLi and other critical threads
-- [ ] Test the API-Endpoints against a local live system under for common vulnerabilities
+- [x] Check for WordPress best-practices
+- [x] Review the code for OWASP Top 10, including XSS, CSRF, SQLi and other critical threats
+- [x] Test the API-Endpoints against a local live system under for common vulnerabilities
+- [x] Fix bugs discovered during security audit
## Future Considerations (v1.0.0+)
@@ -360,5 +361,5 @@ The plugin will provide extensive hooks for customization:
| 0.9.0 | Prometheus Metrics | Complete |
| 0.10.0 | API Endpoints | Complete |
| 0.11.0 | WooCommerce Integration | Complete |
-| 0.12.0 | Security Audit | TBD |
+| 0.12.0 | Security Audit | Complete |
| 1.0.0 | Stable Release | TBD |
diff --git a/src/Api/Controllers/BookingsController.php b/src/Api/Controllers/BookingsController.php
index a8e9915..e14d553 100644
--- a/src/Api/Controllers/BookingsController.php
+++ b/src/Api/Controllers/BookingsController.php
@@ -338,9 +338,9 @@ final class BookingsController extends AbstractController {
$guest_id = $this->find_or_create_guest( $guest );
// Calculate price.
- $price = Calculator::calculate( $room_id, $check_in, $check_out );
- $services = $request->get_param( 'services' ) ?? array();
- $room_price = $price['price'] ?? 0;
+ $calculator = new Calculator( $room_id, $check_in, $check_out );
+ $room_price = $calculator->calculate();
+ $services = $request->get_param( 'services' ) ?? array();
// Calculate services total.
$services_total = 0;
@@ -410,7 +410,7 @@ final class BookingsController extends AbstractController {
// Send notification email.
if ( class_exists( EmailNotifier::class ) ) {
- EmailNotifier::send_admin_notification( $post_id );
+ EmailNotifier::send_admin_new_booking( $post_id );
}
// Prepare response.
@@ -532,7 +532,7 @@ final class BookingsController extends AbstractController {
// Send cancellation email.
if ( class_exists( EmailNotifier::class ) ) {
- EmailNotifier::send_cancellation_email( $id );
+ EmailNotifier::send_cancellation( $id );
}
return $this->formatter->no_content();
@@ -607,7 +607,7 @@ final class BookingsController extends AbstractController {
if ( 'confirmed' === $new_status ) {
update_post_meta( $id, '_bnb_booking_confirmed_at', current_time( 'mysql' ) );
if ( class_exists( EmailNotifier::class ) ) {
- EmailNotifier::send_confirmation_email( $id );
+ EmailNotifier::send_guest_confirmation( $id );
}
}
diff --git a/src/Api/Controllers/PricingController.php b/src/Api/Controllers/PricingController.php
index f7cd6a4..53b0ba0 100644
--- a/src/Api/Controllers/PricingController.php
+++ b/src/Api/Controllers/PricingController.php
@@ -136,9 +136,9 @@ final class PricingController extends AbstractController {
$nights = (int) $check_in_date->diff( $check_out_date )->days;
// Calculate room price.
- $price = Calculator::calculate( $room_id, $check_in, $check_out );
- $room_total = $price['price'] ?? 0;
- $breakdown = $price['breakdown'] ?? array();
+ $calculator = new Calculator( $room_id, $check_in, $check_out );
+ $room_total = $calculator->calculate();
+ $breakdown = $calculator->getBreakdown();
$currency = get_option( 'wp_bnb_currency', 'CHF' );
// Build night-by-night breakdown.
diff --git a/src/Api/Controllers/RoomsController.php b/src/Api/Controllers/RoomsController.php
index 92680fd..14ea363 100644
--- a/src/Api/Controllers/RoomsController.php
+++ b/src/Api/Controllers/RoomsController.php
@@ -400,14 +400,16 @@ final class RoomsController extends AbstractController {
if ( $is_available ) {
// Calculate pricing.
- $price = Calculator::calculate( $room_id, $check_in, $check_out );
+ $calculator = new Calculator( $room_id, $check_in, $check_out );
+ $price = $calculator->calculate();
+ $breakdown = $calculator->getBreakdown();
$data['pricing'] = array(
- 'tier' => $price['breakdown']['tier']->value ?? 'short_term',
- 'base_rate' => $price['breakdown']['base_price_per_night'] ?? 0,
- 'total' => $price['price'] ?? 0,
- 'formatted' => $price['price_formatted'] ?? '',
+ 'tier' => $breakdown['tier']->value ?? 'short_term',
+ 'base_rate' => $breakdown['base_price_per_night'] ?? 0,
+ 'total' => $price,
+ 'formatted' => Calculator::formatPrice( $price ),
'currency' => get_option( 'wp_bnb_currency', 'CHF' ),
- 'breakdown' => $price['breakdown'] ?? array(),
+ 'breakdown' => $breakdown,
);
} else {
// Get conflicts.
diff --git a/src/Booking/EmailNotifier.php b/src/Booking/EmailNotifier.php
index 7fa0f87..eb7e9cf 100644
--- a/src/Booking/EmailNotifier.php
+++ b/src/Booking/EmailNotifier.php
@@ -263,10 +263,10 @@ final class EmailNotifier {
* @return array Guest data with keys: name, first_name, last_name, email, phone, notes, full_address.
*/
private static function get_guest_data( int $booking_id ): array {
- $guest_id = get_post_meta( $booking_id, '_bnb_booking_guest_id', true );
+ $guest_id = (int) get_post_meta( $booking_id, '_bnb_booking_guest_id', true );
// Try to get data from Guest CPT.
- if ( $guest_id ) {
+ if ( $guest_id > 0 ) {
$guest = get_post( $guest_id );
if ( $guest && Guest::POST_TYPE === $guest->post_type ) {
$first_name = get_post_meta( $guest_id, '_bnb_guest_first_name', true );
diff --git a/wp-bnb.php b/wp-bnb.php
index 1331ff9..eb8dc8d 100644
--- a/wp-bnb.php
+++ b/wp-bnb.php
@@ -3,7 +3,7 @@
* Plugin Name: WP BnB Management
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wp-bnb
* Description: A comprehensive Bed & Breakfast management system for WordPress. Manage buildings, rooms, bookings, and guests.
- * Version: 0.11.3
+ * Version: 0.12.0
* Requires at least: 6.0
* Requires PHP: 8.3
* Author: Marco Graetsch
@@ -24,7 +24,7 @@ if ( ! defined( 'ABSPATH' ) ) {
}
// Plugin version constant - MUST match Version in header above.
-define( 'WP_BNB_VERSION', '0.11.3' );
+define( 'WP_BNB_VERSION', '0.12.0' );
// Plugin path constants.
define( 'WP_BNB_PATH', plugin_dir_path( __FILE__ ) );