| Area | Implemented boundary | Evidence / limitation |
|---|---|---|
| SSRF / host policy | Site developer supplies exact HTTPS origins; settings cannot expand them. Only network:request and derived exact hostnames are granted; all production HTTP uses ctx.http.fetch. URL credentials/query/fragment and non-HTTPS configuration are rejected. |
Settings tests and real EmDash private-address rejection test. Host/DNS protections rely on EmDash; private Medusa hosting is unsupported. |
| Secrets | Publishable key uses EmDash secret, host encryption key and write-only settings UI. No Admin secret setting or browser export exists. |
Real runtime test inspects ciphertext, decrypted request header, settings GET and sanitized route output; no encryption key fails closed. Local seed user password and fixture keys are mode 0600 and gitignored. |
| Logs/errors | Plugin logs no keys, raw backend exceptions or error bodies. Public failures return product null; ctx.log records only stable error code and validated product ID, with NOT_FOUND at debug level. Private admin diagnostics remain sanitized. | Tests include key-containing exception and upstream error bodies; browser checks assert no key in admin DOM/storefront HTML. Upstream host logging and operators' infrastructure remain their own boundary. |
| Permissions / browser | Catalog/options/regions require content:read; connection/configuration require plugins:manage. Host CSRF and authorization are used. Only single-product Store data is public. |
Native runtime exercises unauthenticated/scoped/role requests; browser search uses host apiFetch. Publishable keys are intrinsically public Medusa credentials, but this slice retains its configured value on the server. |
| HTML/images/links | React/Astro interpolate escaped text; no arbitrary HTML is rendered. Image URLs accept HTTP(S) with no credentials. Product links default to disabled. Validated site templates fix the HTTPS authority or root-relative route; only :handle is replaced with encoded text. Astro validates href again. | Normalization and renderer tests plus anonymous SSR checks. Images load directly in the browser; set a site CSP/image-origin policy for production privacy needs. |
| Malformed / oversized data | Central normalizer checks envelopes, IDs, titles, arrays, counts, finite non-negative prices and currency shape; discards unnecessary commerce fields. Stream/content-length limit is 1 MiB; list max 100. | Unit malformed/large-body/mixed-currency/tax tests. EmDash currently buffers a host-limited response before returning it; our 1 MiB limit is not a lower transport-memory limit on that host buffer. |
| Timeouts | Five-second deadline includes fetch and body reading, races providers that ignore abort, and signals cancellation. | Timeout tests include a stalled stream. A provider that ignores cancellation can still retain its own resources after the plugin has returned. |
| Redirects | redirect: "error"; explicit 3xx rejected without body. |
Client tests; real gateway redirect rejection. EmDash preserves redirect policy while applying its own host checks. |
| Public development tunnel | Fixed loopback backend; GET-only products/product/regions path allowlist, bounded query/response, only publishable header forwarded, no cookies or Admin/auth. | Ten gateway tests check route/method/header/size/redirect/network boundaries. Temporary public Store exposure is limited to disposable sample data. |
| Public route abuse | IDs validated, bounded response, timeout and no credential access. | No rate limiter/cache is included. Add deployment-level request/concurrency limits and a deliberate commerce cache policy before production. |
Medusa can return 400 both for rejected keys and invalid query/context. The operator code AUTHENTICATION and private admin diagnostics cover “key or request context”; they cannot prove that every 400 is an authentication failure without parsing potentially sensitive backend text. Store prices are catalog display estimates, not confirmed checkout totals or inventory availability.