|
| 1 | +# Adversarial runtime |
| 2 | + |
| 3 | +WP Codebox exposes backend-neutral adversarial campaign and transport-fault |
| 4 | +contracts from `@automattic/wp-codebox-core/public`. The contracts compose with |
| 5 | +the existing fuzz suite, runtime episode, checkpoint, browser, service, artifact, |
| 6 | +and replay surfaces; they do not replace those surfaces. |
| 7 | + |
| 8 | +## Architecture |
| 9 | + |
| 10 | +- `adversarial-campaign.ts` owns deterministic corpus scheduling, generic value |
| 11 | + and action-sequence mutation, novelty retention, bounded concurrent execution, |
| 12 | + minimization, stable finding fingerprints, replay schedules, and differential |
| 13 | + result classification. |
| 14 | +- `transport-faults.ts` owns request matching, deterministic stateful outcome |
| 15 | + sequences, capability negotiation, redacted evidence, and deduplication. It |
| 16 | + does not assume a browser, HTTP library, application, or transport. |
| 17 | +- `adversarial-browser.ts` owns DOM-derived journey planning, hostile generic |
| 18 | + input generation, journey minimization, and user-facing oracle contracts. |
| 19 | +- `adversarial-artifacts.ts` writes bounded manifested finding and replay bundles, |
| 20 | + removes declared secrets and machine-specific paths, and seals corpus/finding |
| 21 | + identity with a deterministic content digest. |
| 22 | +- `runtime-playground` maps supported fault outcomes and browser clock controls |
| 23 | + onto Playwright. Socket behavior that Playwright cannot faithfully produce is |
| 24 | + reported as unsupported. |
| 25 | +- `runtime-services.ts` provisions loopback-only, ephemeral MySQL/MariaDB, Redis, |
| 26 | + SMTP sink, and HTTP fixture services. Every service has idempotent teardown and |
| 27 | + typed disruption controls. |
| 28 | + |
| 29 | +Generic layers contain no component, plugin, vendor integration, or product |
| 30 | +policy. WordPress grammars and attack packs remain extension responsibilities. |
| 31 | + |
| 32 | +## Campaign contract |
| 33 | + |
| 34 | +```ts |
| 35 | +const campaign = adversarialCampaign({ |
| 36 | + id: "component-campaign", |
| 37 | + seed: "ci-seed-1", |
| 38 | + corpus: [{ |
| 39 | + id: "create-update-read", |
| 40 | + actions: [ |
| 41 | + { type: "create", input: { title: "seed" } }, |
| 42 | + { type: "update", input: { title: "candidate" } }, |
| 43 | + { type: "read" }, |
| 44 | + ], |
| 45 | + }], |
| 46 | + budgets: { |
| 47 | + maxCases: 1000, |
| 48 | + workers: 4, |
| 49 | + maxCaseTimeMs: 30000, |
| 50 | + maxWallTimeMs: 300000, |
| 51 | + }, |
| 52 | + oracles: [{ |
| 53 | + schema: "wp-codebox/adversarial-oracle/v1", |
| 54 | + id: "state-integrity", |
| 55 | + severity: "high", |
| 56 | + }], |
| 57 | +}) |
| 58 | + |
| 59 | +const result = await runAdversarialCampaign(campaign, { |
| 60 | + execute: async (plan, signal) => adapter.execute(plan, signal), |
| 61 | + evaluate: async (plan, observation, oracles) => |
| 62 | + adapter.evaluate(plan, observation, oracles), |
| 63 | +}) |
| 64 | +``` |
| 65 | + |
| 66 | +Workers execute a deterministic round concurrently. Results are committed to the |
| 67 | +corpus in case-index order after every round, so completion timing cannot change |
| 68 | +the retained corpus or later mutations. Replay metadata records the seed, |
| 69 | +worker, iteration, matrix cell, minimized actions/input, fault schedule, exact |
| 70 | +provenance, normalized schedule, and a one-command replay instruction. |
| 71 | + |
| 72 | +## Mutation and novelty |
| 73 | + |
| 74 | +Core mutation supports generic scalar, structured, binary, and stateful sequence |
| 75 | +inputs. Mutation targets are selected from stable JSON paths using a seeded |
| 76 | +SHA-256 schedule. Extensions can represent multipart, serialized, markup, or |
| 77 | +domain-specific inputs as structured actions and register richer mutators at the |
| 78 | +adapter boundary without adding those grammars to runtime-core. |
| 79 | + |
| 80 | +Adapters return bounded novelty signals such as coverage edges, route ids, state |
| 81 | +digests, hook ids, or query fingerprints. New signals retain a case in the |
| 82 | +interesting corpus. Minimization removes action chunks and shrinks input values, |
| 83 | +replaying each candidate through the same adapter and oracle set. |
| 84 | + |
| 85 | +## Fault model |
| 86 | + |
| 87 | +```json |
| 88 | +{ |
| 89 | + "schema": "wp-codebox/transport-fault-model/v1", |
| 90 | + "seed": "fault-seed-1", |
| 91 | + "rules": [{ |
| 92 | + "id": "verification-outage", |
| 93 | + "match": { |
| 94 | + "host": "service.example", |
| 95 | + "method": "POST", |
| 96 | + "path": "/verify" |
| 97 | + }, |
| 98 | + "sequence": [ |
| 99 | + { "status": 500 }, |
| 100 | + { "delayMs": 30000, "timeoutMs": 1000 }, |
| 101 | + { "status": 200, "body": "malformed", "truncateAfterBytes": 4 } |
| 102 | + ], |
| 103 | + "repeat": "last" |
| 104 | + }] |
| 105 | +} |
| 106 | +``` |
| 107 | + |
| 108 | +Each adapter publishes `wp-codebox/transport-fault-capabilities/v1`. Campaigns |
| 109 | +must negotiate before execution and fail closed when a required semantic is |
| 110 | +unsupported. |
| 111 | + |
| 112 | +### Playwright fidelity |
| 113 | + |
| 114 | +| Semantic | Fidelity | |
| 115 | +| --- | --- | |
| 116 | +| Response status/header/body substitution | Exact | |
| 117 | +| Delay, deterministic jitter, host remap | Exact | |
| 118 | +| Malformed/truncated payload, bandwidth, timeout, refusal/reset | Emulated and labeled | |
| 119 | +| Chunk framing, half-close, disconnect-after-N-bytes | Unsupported | |
| 120 | + |
| 121 | +Playwright owns HTTP framing and sockets, so payload truncation must not be |
| 122 | +reported as a transport disconnect. A lower-level proxy provider is required for |
| 123 | +those exact semantics. |
| 124 | + |
| 125 | +## Services |
| 126 | + |
| 127 | +Recipe `inputs.services` accepts: |
| 128 | + |
| 129 | +- `mysql`: MySQL 8.4 or MariaDB 11.4, preserving existing output and readiness |
| 130 | + behavior. |
| 131 | +- `redis`: ephemeral Redis with persistence disabled. |
| 132 | +- `smtp`: SMTP message sink plus its loopback inspection port. |
| 133 | +- `http`: deterministic loopback response fixture. |
| 134 | + |
| 135 | +The provisioned service set exposes `control(serviceId, action, options)`. |
| 136 | +Container lifecycle actions (`stop`, `start`, `pause`, `resume`, `restart`, |
| 137 | +`disconnect`, and `reconnect`) have exact Docker fidelity. Provider-specific |
| 138 | +`flush` and read-only controls are exact where implemented. Network latency is |
| 139 | +explicitly unsupported by this provider and should use the transport-fault |
| 140 | +adapter instead. |
| 141 | + |
| 142 | +All containers bind ephemeral loopback ports, use no persistent volumes, and are |
| 143 | +removed in reverse order after success, failure, cancellation, or timeout. |
| 144 | + |
| 145 | +## Clock control |
| 146 | + |
| 147 | +Playwright browser time supports exact freeze, advance, skew, and resume through |
| 148 | +its clock API. The same capability response explicitly marks server process, |
| 149 | +scheduler, and database clocks unsupported. A WordPress runtime extension is |
| 150 | +required to control those surfaces without faking server behavior in browser |
| 151 | +JavaScript. |
| 152 | + |
| 153 | +## Browser oracles |
| 154 | + |
| 155 | +The generic browser planner derives actions from visible control descriptors and |
| 156 | +generates empty, oversized, hostile punctuation, bidi, and Unicode inputs. Submit |
| 157 | +controls are repeated to expose duplicate side effects. Generic oracles cover: |
| 158 | + |
| 159 | +- page errors, console errors, and unhandled rejections; |
| 160 | +- controls that accept input without an observable action; |
| 161 | +- loading indicators that exceed a declared threshold; |
| 162 | +- clipping, viewport overflow, and invisible focus; |
| 163 | +- adapter-provided accessibility violations; |
| 164 | +- duplicate observable effects. |
| 165 | + |
| 166 | +Journey minimization uses deterministic subset replay to retain the shortest |
| 167 | +sequence that preserves the oracle failure. |
| 168 | + |
| 169 | +## Evidence and safety |
| 170 | + |
| 171 | +`writeAdversarialEvidenceBundle()` writes a manifest, campaign result, one |
| 172 | +finding and replay document per stable fingerprint, and a secret-scan report. |
| 173 | +The writer enforces an artifact byte ceiling, redacts sensitive fields and |
| 174 | +caller-declared values, removes machine-specific paths, and records SHA-256 file |
| 175 | +digests plus a stable bundle content digest. |
| 176 | + |
| 177 | +Campaign budgets independently bound cases, actions, input bytes, per-case and |
| 178 | +wall time, worker count, and artifact bytes. Adapters remain responsible for the |
| 179 | +existing Codebox CPU, memory, disk, process, network-deny, mount, and disposable |
| 180 | +sandbox boundaries. |
| 181 | + |
| 182 | +## Compatibility |
| 183 | + |
| 184 | +Existing recipes and `fuzzRun` cases are unchanged. Existing MySQL defaults, |
| 185 | +outputs, readiness, and teardown behavior remain compatible. The new service |
| 186 | +kinds and public TypeScript contracts are additive. |
| 187 | + |
| 188 | +## Current boundaries |
| 189 | + |
| 190 | +The following capabilities are intentionally not claimed by this change: |
| 191 | + |
| 192 | +- exact socket framing faults require a lower-level proxy provider; |
| 193 | +- server-side WordPress HTTP fault interception requires a WordPress extension |
| 194 | + adapter using the generic fault contract; |
| 195 | +- PHP/WordPress, cron, and database clock control require a WordPress extension; |
| 196 | +- WordPress-specific mutation grammars, security policies, and instrumentation |
| 197 | + remain extension-owned; |
| 198 | +- live vulnerable plugin/theme discovery campaigns require disposable runtime |
| 199 | + fixtures and are not replaced by the neutral contract tests. |
| 200 | + |
| 201 | +These are capability gaps, not silent skips. Consumers can inspect negotiation |
| 202 | +results before running a campaign. |
0 commit comments