Files
igny8/docs/audits/INTEGRATION-SECURITY-AUDIT-2026-01-13.md
2026-01-13 08:19:55 +00:00

24 KiB

IGNY8 ↔ WordPress Integration Complete Security Audit

Audit Date: 2026-01-13 Auditor: System Audit (Documentation Calibrated) Scope: Backend (Django) + WordPress Plugin + Documentation Review Status: COMPLETE - CALIBRATED WITH DOCUMENTATION


EXECUTIVE SUMMARY

This comprehensive audit examined the IGNY8 backend and WordPress plugin integration system, cross-referencing against all existing documentation. The audit identified 17 security vulnerabilities (3 critical, 5 high, 6 medium, 3 low), 6 unused/redundant database fields, 2 major data duplication issues, and several documentation vs. implementation gaps.

Key Findings:

  • API keys stored in plain text (no encryption) despite documentation claims
  • Public endpoints expose sensitive configuration data
  • Timing attack vulnerability in Bearer token validation
  • Documentation states "credentials encrypted at rest" but encryption NOT implemented
  • Several fields documented but never used
  • Missing scheduled publishing task from Celery Beat (documented but not configured)

PART 1: DOCUMENTATION VS IMPLEMENTATION GAPS

1.1 Critical Discrepancies Found

Documentation Claim Reality Impact
"API keys encrypted at rest" (INTEGRATIONS.md) Plain text CharField in database CRITICAL - False security assumption
"Webhook signature verification" (INTEGRATIONS.md) No signature verification implemented HIGH - Anyone with key can spoof webhooks
"Rate limiting on webhook endpoint" (INTEGRATIONS.md) Webhooks explicitly disable throttling (NoThrottle) HIGH - DoS possible
"auto_publish_enabled triggers WordPress push" Automation Stage 7 sets status but does NOT trigger WP push MEDIUM - Manual publish required
"Scheduled auto-publish task active" Task exists but NOT in Celery Beat schedule MEDIUM - Content sits unpublished
SiteIntegration.api_key field (INTEGRATIONS.md line 97) API key stored in Site.wp_api_key, NOT SiteIntegration Documentation outdated
"credentials_json stores WordPress credentials" credentials_json is empty for WordPress; Site.wp_api_key is source of truth Documentation outdated

1.2 Documentation Accuracy Summary

Document Accuracy Issues Found
WORDPRESS-INTEGRATION-FLOW.md 90% Accurate on auth, slight gaps on scheduling
WORDPRESS-INTEGRATION.md 85% Missing encryption truth, good on plugin distribution
PUBLISHER.md 95% Accurate on models and flows
INTEGRATIONS.md 70% Contains outdated field references, false encryption claim
SCHEDULED-CONTENT-PUBLISHING.md 95% Accurate on Celery tasks, correctly notes site_status vs status
CONTENT-PIPELINE.md 90% Accurate on stages, slight gap on Stage 8 auto-publish

PART 2: SYSTEM ARCHITECTURE (VERIFIED)

2.1 Actual Data Flow (Verified Against Code)

IGNY8 Backend                           WordPress Plugin
┌──────────────────────┐                ┌──────────────────────┐
│ Site Model           │                │ wp_options           │
│ ├─ wp_api_key ◄──────┼────────────────┼─► igny8_api_key      │
│ │   (SINGLE SOURCE)  │                │    igny8_site_id     │
│ ├─ domain            │                │    igny8_integration_id│
│ └─ wp_url (LEGACY)   │                │                      │
│                      │                │ Post Meta            │
│ SiteIntegration      │                │ ├─ _igny8_content_id │
│ ├─ config_json       │                │ ├─ _igny8_task_id    │
│ │   (site_url only)  │                │ └─ _igny8_last_synced│
│ ├─ credentials_json  │                │                      │
│ │   (EMPTY for WP!)  │                │                      │
│ └─ sync_status       │                │                      │
│                      │                │                      │
│ Content Model        │                │                      │
│ ├─ external_id ◄─────┼────────────────┼─► post_id            │
│ ├─ external_url      │                │                      │
│ ├─ status (editorial)│                │                      │
│ └─ site_status       │                │                      │
│     (publishing)     │                │                      │
└──────────────────────┘                └──────────────────────┘

2.2 Authentication Flow (Verified)

IGNY8 → WordPress:

# publisher_service.py (line 136, 144)
destination_config = {
    'api_key': site.wp_api_key,  # FROM SITE MODEL
    'site_url': site.domain or site.wp_url  # Fallback to legacy
}

# wordpress_adapter.py
headers = {
    'X-IGNY8-API-KEY': api_key,  # Primary method
    'Content-Type': 'application/json'
}

WordPress → IGNY8:

// Plugin: check_permission() method
$stored_api_key = get_option('igny8_api_key');
$header_api_key = $request->get_header('x-igny8-api-key');

// CORRECT: hash_equals for X-IGNY8-API-KEY
if (hash_equals($stored_api_key, $header_api_key)) return true;

// VULNERABLE: strpos for Bearer token
if (strpos($auth_header, 'Bearer ' . $stored_api_key) !== false) return true;

2.3 API Key Generation (Verified)

POST /api/v1/integration/integrations/generate-api-key/
Body: { "site_id": 123 }

Key Format: igny8_site_{site_id}_{timestamp_ms}_{random_10_chars}
Example:    igny8_site_123_1736780400000_a7b9c3d2e1

Storage: Site.wp_api_key (CharField, plain text, max 255)
Recovery: NOT POSSIBLE - shown once on generation
Revocation: Sets Site.wp_api_key = None

PART 3: CONTENT STATUS SYSTEM (DOCUMENTED CORRECTLY)

3.1 Two-Status Architecture

The documentation correctly describes the dual-status system:

Field Purpose Values
Content.status Editorial workflow draft → review → approved → (published - legacy)
Content.site_status WordPress publishing not_published → scheduled → publishing → published / failed

3.2 Publishing Flow Paths

Path 1: Manual Publish (Working)

User clicks "Publish" → ContentViewSet.publish_to_wordpress
    → Celery task: publish_content_to_wordpress
    → WordPress API call
    → Update external_id, external_url, site_status='published'

Path 2: Scheduled Publish (Partially Working)

schedule_approved_content (hourly)
    → Find approved content with site_status='not_published'
    → Assign scheduled_publish_at
    → Set site_status='scheduled'

process_scheduled_publications (every 5 min)
    → Find content where scheduled_publish_at <= now
    → Queue publish_content_to_wordpress task
    → WordPress API call

Path 3: Automation Stage 7 (NOT TRIGGERING WP PUSH)

Automation Stage 7:
    → Content.status = 'published' (legacy status change)
    → NO site_status change
    → NO WordPress API call queued
    → Content sits with site_status='not_published'

GAP IDENTIFIED: Automation Stage 7 does NOT call publishing scheduler or queue WordPress task.


PART 4: SECURITY VULNERABILITIES (VERIFIED)

4.1 CRITICAL ISSUES

CRITICAL-1: API Keys Stored in Plain Text

Location: Backend - Site.wp_api_key field (auth/models.py line 491) Documentation Claim: "Credentials encrypted at rest" (INTEGRATIONS.md line 345) Reality: CharField stores plain text - NO encryption

Evidence:

# auth/models.py
wp_api_key = models.CharField(max_length=255, blank=True, null=True,
    help_text="API key for WordPress integration via IGNY8 WP Bridge plugin")

Impact: Database compromise exposes ALL WordPress API keys Risk Score: 9/10


CRITICAL-2: Timing Attack in Bearer Token Validation

Location: Plugin - class-igny8-rest-api.php:140 Impact: API key can be guessed character-by-character

Vulnerable Code:

// Uses strpos (VULNERABLE)
if (strpos($auth_header, 'Bearer ' . $stored_api_key) !== false)

// Should use hash_equals (SAFE)
if (hash_equals($stored_api_key, substr($auth_header, 7)))

Risk Score: 8/10


CRITICAL-3: Diagnostic Logging Exposes Sensitive Data

Location: Plugin - class-igny8-rest-api.php:533-565 Impact: Full request bodies logged including all content

Evidence:

error_log('========== RAW REQUEST BODY ==========');
error_log($raw_body);
error_log('========== PARSED JSON DATA ==========');
error_log(print_r($content_data, true));

Risk Score: 8/10


4.2 HIGH SEVERITY ISSUES

HIGH-1: Public Endpoints Expose Configuration

Verified Endpoints:

Endpoint Permission Data Exposed
/wp-json/igny8/v1/status __return_true (PUBLIC) has_api_key, connected, versions
/wp-json/igny8/v1/site-metadata/ __return_true (PUBLIC) post_types, taxonomies, counts

Note: Documentation (WORDPRESS-INTEGRATION-FLOW.md line 86-91) does NOT flag these as security issues.


HIGH-2: Permission Architecture Inconsistency

Location: test_connection_collection endpoint Permission: AllowAny (then checks auth manually inside view)

Documentation: Does not mention this inconsistency.


HIGH-3: No Webhook Signature Verification

Documentation Claim: "Optional signature verification" (INTEGRATIONS.md line 349) Reality: Only API key validation, NO HMAC signature verification

Code Evidence (webhooks.py):

api_key = request.headers.get('X-IGNY8-API-KEY')
if not stored_api_key or stored_api_key != api_key:
    return error_response('Invalid API key', ...)
# NO signature validation

HIGH-4: Webhooks Disable Rate Limiting

Documentation Claim: "Rate limiting on webhook endpoint" (INTEGRATIONS.md line 351) Reality: Webhooks explicitly use NoThrottle class


HIGH-5: Encryption Falls Back Silently

Location: Plugin - functions.php:31-48 Impact: Admin unaware if encryption fails


4.3 MEDIUM SEVERITY ISSUES

  1. SSRF Risk in Connection Testing - No IP validation
  2. credentials_json Exposed in Serializer - All fields returned
  3. Flexible ID Lookup Enables Enumeration - /post-status/{id} accepts both IDs
  4. Excessive API Request Logging - Keys in logs
  5. WordPress URL Not Validated - No HTTPS enforcement
  6. API Key Partially Visible - /verify-key shows 15-char prefix

4.4 LOW SEVERITY ISSUES

  1. Version disclosure in multiple endpoints
  2. No CORS headers defined on plugin endpoints
  3. Webhook logs stored long-term (500 entries)

PART 5: REST API ENDPOINTS INVENTORY (VERIFIED)

5.1 Backend Endpoints (Verified Against urls.py)

Endpoint Method Permission Documentation
/api/v1/integration/integrations/ CRUD IsAuthenticatedAndActive + IsEditorOrAbove Correct
/api/v1/integration/integrations/test-connection/ (collection) POST AllowAny NOT documented as AllowAny
/api/v1/integration/integrations/{id}/test-connection/ POST Authenticated Correct
/api/v1/integration/integrations/generate-api-key/ POST Authenticated Correct
/api/v1/integration/integrations/revoke-api-key/ POST Authenticated Correct
/api/v1/integration/webhooks/wordpress/status/ POST AllowAny (header auth) Correct
/api/v1/integration/webhooks/wordpress/metadata/ POST AllowAny (header auth) Correct
/api/v1/publisher/publish/ POST Authenticated Correct
/api/v1/writer/content/{id}/publish_to_wordpress/ POST Authenticated Correct
/api/v1/writer/content/{id}/schedule/ POST Authenticated Correct (v1.3.2)
/api/v1/writer/content/{id}/unschedule/ POST Authenticated Correct (v1.3.2)

5.2 Plugin Endpoints (Verified Against class-igny8-rest-api.php)

Endpoint Method Permission Documentation Match
/wp-json/igny8/v1/status GET PUBLIC Not flagged as security issue
/wp-json/igny8/v1/site-metadata/ GET PUBLIC (internal check) Not flagged
/wp-json/igny8/v1/verify-key GET check_permission Correct
/wp-json/igny8/v1/publish POST check_permission Correct
/wp-json/igny8/v1/post-by-content-id/{id} GET check_permission Correct
/wp-json/igny8/v1/post-by-task-id/{id} GET check_permission Correct
/wp-json/igny8/v1/post-status/{id} GET check_permission Correct
/wp-json/igny8/v1/event POST verify_webhook_secret Correct

PART 6: DATA STORAGE ANALYSIS (VERIFIED)

6.1 Site Model Fields (auth/models.py)

Field Documentation Status Actual Usage Action
wp_api_key Documented as primary ACTIVE - single source of truth KEEP + ENCRYPT
domain Documented ACTIVE - WordPress URL KEEP
wp_url Documented as "deprecated" LEGACY - fallback in publisher_service EVALUATE for removal
wp_username Documented as "deprecated" ZERO USAGE in codebase REMOVE
wp_app_password Documented as "deprecated" ZERO USAGE in codebase REMOVE

6.2 SiteIntegration Model Fields

Field Documentation Status Actual Usage Action
site Documented ACTIVE KEEP
platform Documented ACTIVE ('wordpress') KEEP
config_json Documented as storing URL ACTIVE (site_url only) KEEP
credentials_json Documented as storing creds EMPTY for WordPress Documentation outdated
sync_status Documented ACTIVE KEEP

Documentation Gap: INTEGRATIONS.md line 97-98 shows api_key and username as SiteIntegration fields, but WordPress actually uses Site.wp_api_key.

6.3 Content Model Fields

Field Documentation Status Actual Usage Action
status Correctly documented as editorial ACTIVE KEEP
site_status Correctly documented as publishing ACTIVE KEEP
external_id Documented ACTIVE - WordPress post ID KEEP
external_url Documented ACTIVE - WordPress URL KEEP
external_type Not documented NEVER USED REMOVE
external_metadata Not documented Only set to {} REMOVE
sync_status (on Content) Not documented NEVER USED REMOVE

6.4 ContentTaxonomy Model Fields

Field Documentation Status Actual Usage Action
external_id Documented ACTIVE KEEP
external_taxonomy Documented ACTIVE KEEP
sync_status Not documented NEVER USED REMOVE

PART 7: CELERY TASKS STATUS (VERIFIED)

7.1 Scheduled Tasks (celery.py Beat Schedule)

Task Schedule Documentation Status
schedule_approved_content Every hour Documented (SCHEDULED-CONTENT-PUBLISHING.md) ACTIVE
process_scheduled_publications Every 5 min Documented ACTIVE
publish_content_to_wordpress On-demand Documented ACTIVE

7.2 Missing Tasks (Documented but NOT Scheduled)

process_pending_wordpress_publications - WORDPRESS-INTEGRATION-FLOW.md mentions this task exists but notes:

"CURRENT STATUS: This task is NOT in Celery Beat schedule!"

This matches the documentation correctly.


PART 8: PLUGIN DISTRIBUTION SYSTEM (VERIFIED)

8.1 Plugin Models (Correctly Documented)

Model Documentation Implementation Match
Plugin WORDPRESS-INTEGRATION.md plugins/models.py
PluginVersion Documented with all fields Matches implementation
PluginInstallation Documented Matches implementation
PluginDownload Documented Matches implementation

8.2 Plugin API Endpoints (Correctly Documented)

Endpoint Documentation Implementation
/api/plugins/{slug}/download/ INDEX.md line 24 Working
/api/plugins/{slug}/check-update/ INDEX.md line 25 Working
/api/plugins/{slug}/info/ INDEX.md line 26 Working
/api/plugins/{slug}/register/ INDEX.md line 27 Working
/api/plugins/{slug}/health-check/ INDEX.md line 28 Working

PART 9: UNUSED/DEAD CODE INVENTORY

9.1 Fields to Remove (Verified Zero Usage)

Model Field Evidence
Site wp_username grep -r "wp_username" --include="*.py" = 0 results
Site wp_app_password grep -r "wp_app_password" --include="*.py" = 0 results
Content external_type Never read in codebase
Content external_metadata Only set to {} in publisher_service.py
Content sync_status SiteIntegration.sync_status used instead
ContentTaxonomy sync_status Never read or written

9.2 Redundant Code Paths

  1. Duplicate Connection Testing - Two endpoints with same logic
  2. Duplicate API Key Validation - Same validation in 4+ files
  3. Dead Admin Bulk Actions - bulk_trigger_sync(), bulk_test_connection() have TODO comments

9.3 Plugin Unused Options

Option Status
igny8_access_token Redundant with igny8_api_key
igny8_access_token_issued Referenced but not used in auth

PART 10: DATA DUPLICATION ISSUES

10.1 API Key Duplication

Location Field Role
Django Site model wp_api_key PRIMARY (single source of truth)
Django SiteIntegration credentials_json EMPTY for WordPress
WordPress igny8_api_key COPY (must match primary)
WordPress igny8_access_token REDUNDANT (should remove)

Risk: Documentation mentions credentials_json but WordPress doesn't use it.

10.2 URL Duplication

Location Field Role
Site.domain Primary ACTIVE
Site.wp_url Legacy Fallback only
SiteIntegration.config_json['site_url'] Configuration ACTIVE

PART 11: RECOMMENDATIONS

Immediate Actions (Critical)

Priority Issue Action Documentation Update
1 Plain text API keys Implement field encryption Update INTEGRATIONS.md
2 Timing attack (strpos) Use hash_equals everywhere Update plugin docs
3 Diagnostic logging Remove or conditional Update plugin docs
4 Public endpoints Secure /status and /site-metadata Update WORDPRESS-INTEGRATION-FLOW.md

Short-term Actions (High)

Priority Issue Action Documentation Update
5 Permission inconsistency Fix test_connection_collection Update ENDPOINTS.md
6 No webhook signatures Implement HMAC verification Update INTEGRATIONS.md
7 No rate limiting Enable throttling on webhooks Update INTEGRATIONS.md
8 API key in response Remove from verify-key N/A

Documentation Updates Required

Document Updates Needed
INTEGRATIONS.md Remove "encrypted at rest" claim, fix field references
WORDPRESS-INTEGRATION-FLOW.md Flag public endpoints as security concern
ENDPOINTS.md Note AllowAny on test-connection collection

Cleanup Actions

Action Fields to Remove Migration Required
Remove from Site wp_username, wp_app_password Yes
Remove from Content external_type, external_metadata, sync_status Yes
Remove from ContentTaxonomy sync_status Yes
Remove from Plugin igny8_access_token option Plugin update

PART 12: PUBLISHING WORKFLOW SUMMARY

What's Working

Flow Status Notes
Manual publish button Working ContentViewSet.publish_to_wordpress
Scheduled publishing Working Celery Beat tasks active
Plugin distribution Working Auto-update mechanism functional
Webhook status sync Working WordPress → IGNY8 updates

What's NOT Working

Flow Status Issue
Automation Stage 7 → WP Broken Sets status but no WP push
Content update sync Missing No republish capability
WordPress → IGNY8 import Missing No pull sync feature

Documented But Not Implemented

Feature Documentation Reference Status
Webhook signature verification INTEGRATIONS.md line 349 NOT implemented
Webhook rate limiting INTEGRATIONS.md line 351 NOT implemented
Credential encryption INTEGRATIONS.md line 345 NOT implemented

APPENDIX A: FILES AUDITED

Backend Files

  • backend/igny8_core/auth/models.py - Site model, wp_api_key
  • backend/igny8_core/business/integration/models.py - SiteIntegration, SyncEvent
  • backend/igny8_core/business/publishing/models.py - PublishingRecord
  • backend/igny8_core/business/content/models.py - Content, external fields
  • backend/igny8_core/modules/integration/views.py - API endpoints
  • backend/igny8_core/modules/integration/webhooks.py - Webhook handlers
  • backend/igny8_core/business/publishing/services/publisher_service.py
  • backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py
  • backend/igny8_core/tasks/publishing_scheduler.py - Celery tasks
  • backend/igny8_core/tasks/wordpress_publishing.py - Publishing task
  • backend/igny8_core/celery.py - Beat schedule

Plugin Files

  • plugins/wordpress/source/igny8-wp-bridge/includes/class-igny8-rest-api.php
  • plugins/wordpress/source/igny8-wp-bridge/includes/class-igny8-api.php
  • plugins/wordpress/source/igny8-wp-bridge/includes/class-igny8-webhooks.php
  • plugins/wordpress/source/igny8-wp-bridge/includes/functions.php

Documentation Files Reviewed

  • docs/60-PLUGINS/WORDPRESS-INTEGRATION.md
  • docs/60-PLUGINS/INDEX.md
  • docs/60-PLUGINS/PLUGIN-UPDATE-WORKFLOW.md
  • docs/50-DEPLOYMENT/WORDPRESS-INTEGRATION-FLOW.md
  • docs/10-MODULES/PUBLISHER.md
  • docs/10-MODULES/INTEGRATIONS.md
  • docs/40-WORKFLOWS/CONTENT-PIPELINE.md
  • docs/40-WORKFLOWS/SCHEDULED-CONTENT-PUBLISHING.md
  • docs/00-SYSTEM/ARCHITECTURE.md
  • docs/20-API/ENDPOINTS.md

APPENDIX B: VULNERABILITY SEVERITY MATRIX

ID Title CVSS Documentation Claim Reality
CRITICAL-1 Plain text API keys 9.0 "Encrypted at rest" Plain text CharField
CRITICAL-2 Timing attack 8.0 Not mentioned strpos vulnerability
CRITICAL-3 Diagnostic logging 8.0 Not mentioned Full request bodies logged
HIGH-1 Public endpoints 7.0 Not flagged Information disclosure
HIGH-2 Permission inconsistency 6.5 Not documented AllowAny misuse
HIGH-3 No webhook signatures 6.0 "Optional" Not implemented at all
HIGH-4 No rate limiting 5.5 "Rate limiting enabled" NoThrottle class used
HIGH-5 Silent encryption fail 6.0 Not mentioned Falls back to plain text

End of Calibrated Audit Report

Next Steps:

  1. Review findings with development team
  2. Prioritize critical security fixes
  3. Update documentation to match reality
  4. Create migration plan for field removal
  5. Implement encryption before production