# Security — magento-orderwebhook

> **Last verified:** 2026-02-26 (Europe/London)
> **Owner:** Engineering
> **Related:** [GDPR.md](GDPR.md) | [AUDIT.md](AUDIT.md) | [DATA_OWNERSHIP.md](DATA_OWNERSHIP.md)

---

## Authentication and Transport Controls

| Control | Status | Evidence |
|---------|--------|----------|
| API key authentication (Bearer token) | PRESENT | `src/Model/WebhookSender.php` — `Authorization: Bearer` header |
| API key encrypted at rest | PRESENT | `src/etc/adminhtml/system.xml` — `backend_model: Magento\Config\Model\Config\Backend\Encrypted` |
| HTTPS transport | ASSUMED — default NGauge URL uses HTTPS | `src/etc/config.xml` — `https://ahshub.portalpeople.app` |
| Retry with exponential backoff | PRESENT | `src/Model/WebhookSender.php` — `sleep(pow(2, $attempt))` |
| Re-entrancy guard (prevents recursive save loop) | PRESENT | `src/Model/WebhookSender.php` — `private static bool $sending = false` |
| Admin resend ACL check | PRESENT | `src/Controller/Adminhtml/Order/ResendWebhook.php` — `PortalPeople_OrderWebhook::config` |
| Observer exceptions contained | PRESENT | Both observers wrap `execute()` in try/catch |

---

## Security Findings

### S1 — MEDIUM: ObjectManager bypasses DI — EncryptorInterface not testable

| Attribute | Detail |
|-----------|--------|
| Severity | MEDIUM |
| File | `src/Model/WebhookSender.php` |

`WebhookSender::sendRequest()` resolves `EncryptorInterface` via `\Magento\Framework\App\ObjectManager::getInstance()`. This means the decryption path cannot be unit-tested with mock dependencies. Any regression in key decryption would only be caught in integration testing.

**Recommendation:** Inject `EncryptorInterface` via constructor.

---

### S2 — MEDIUM: Customer DOB and gender sent to external system

| Attribute | Detail |
|-----------|--------|
| Severity | MEDIUM |
| File | `src/Model/OrderDataBuilder.php` |

Customer date of birth and gender are included in every webhook payload to NGauge. Under UK/EU GDPR, DOB can constitute special-category data depending on context. No GDPR legal basis, consent mechanism, or signed Data Processing Agreement (DPA) reference is documented within this module.

**Recommendation:** Document legal basis and DPA status in `docs/GDPR.md`. If no lawful basis exists, remove DOB and gender from the payload.

---

### S3 — MEDIUM: Partial card details in webhook payload

| Attribute | Detail |
|-----------|--------|
| Severity | MEDIUM |
| File | `src/Model/OrderDataBuilder.php` |

The webhook payload includes `payment.cc_last4`, `payment.cc_type`, `payment.cc_exp_month`, and `payment.cc_exp_year`. While these are partial details (not full card numbers), transmitting them to a third party may have PCI-DSS implications depending on scope.

`UNKNOWN — Requires Clarification`: Has PCI-DSS scope been assessed for transmitting these fields to NGauge?

**Recommendation:** Confirm PCI-DSS scope with security/compliance team. If in doubt, remove card fields from the payload.

---

### S4 — LOW: API key decryption failure not explicitly handled

| Attribute | Detail |
|-----------|--------|
| Severity | LOW |
| File | `src/Model/WebhookSender.php` |

`EncryptorInterface::decrypt()` may throw an exception if the key material is corrupt or the encryption key has rotated. There is no visible try/catch around the decryption call in `sendRequest()`. A decryption failure would propagate as an unhandled exception.

`UNKNOWN — Requires Clarification`: Is this exception caught by the observer's outer try/catch? Does it result in silent failure or a visible error?

**Recommendation:** Add explicit try/catch around decryption; log a clear error if decryption fails.

---

### S5 — LOW: Deprecated Registry used in admin block

| Attribute | Detail |
|-----------|--------|
| Severity | LOW |
| File | `src/Block/Adminhtml/Order/NgaugeFields.php` |

`Magento\Framework\Registry` is deprecated since Magento 2.3. While not a direct security risk, deprecated APIs are removed in future versions and their use indicates technical debt in security-adjacent admin code.

---

## Personal Data Summary

| Data | Category | Source | Sent To | Locally Stored |
|------|----------|--------|---------|----------------|
| Customer email | Personal data (PII) | `sales_order` | NGauge Platform | No (in order) |
| Customer first/last name | Personal data (PII) | `sales_order` | NGauge Platform | No (in order) |
| Customer DOB | Special-category / sensitive | `customer_entity` | NGauge Platform | No |
| Customer gender | Personal data | `customer_entity` | NGauge Platform | No |
| Billing address + telephone | Personal data (PII) | `sales_order` | NGauge Platform | No (in order) |
| Shipping address + telephone | Personal data (PII) | `sales_order` | NGauge Platform | No (in order) |
| cc_last4 / type / expiry | Financial data (partial) | `sales_payment` | NGauge Platform | No |
| `ngauge_order_no` | Non-personal | This module | N/A | `sales_order` |
| `ngauge_order_id` | Non-personal | This module | N/A | `sales_order` |
| `ngauge_order_link` | Non-personal | This module | N/A | `sales_order` |

---

## Secrets Management

| Secret | Storage | Rotation |
|--------|---------|---------|
| NGauge API key | Magento encrypted `core_config_data` (`Magento\Config\Model\Config\Backend\Encrypted`) | Via Magento admin — no code change needed |

`UNKNOWN — Requires Clarification`: What is the Magento encryption key rotation procedure for this environment?

---

## Unknowns

1. `UNKNOWN — Requires Clarification`: Has a GDPR legal basis been established for transmitting customer DOB and gender to NGauge?
2. `UNKNOWN — Requires Clarification`: Is NGauge operating as a data processor under a signed DPA?
3. `UNKNOWN — Requires Clarification`: Has PCI-DSS scope been assessed for the partial card data fields in the payload?
4. `UNKNOWN — Requires Clarification`: Is there infrastructure-level encryption or WAF in front of the NGauge endpoint?
5. `UNKNOWN — Requires Clarification`: What is the Magento encryption key rotation procedure for this environment?

---

## Evidence

- `/modules/magento-orderwebhook/src/Model/WebhookSender.php`
- `/modules/magento-orderwebhook/src/Model/OrderDataBuilder.php`
- `/modules/magento-orderwebhook/src/Controller/Adminhtml/Order/ResendWebhook.php`
- `/modules/magento-orderwebhook/src/etc/adminhtml/system.xml`
- `/modules/magento-orderwebhook/src/etc/acl.xml`
- `/modules/magento-orderwebhook/src/etc/config.xml`
- `/modules/magento-orderwebhook/src/Block/Adminhtml/Order/NgaugeFields.php`
