Skip to content

Commit caa0192

Browse files
committed
fix: enforce adversarial HTTP action contracts
1 parent 051305a commit caa0192

2 files changed

Lines changed: 13 additions & 2 deletions

File tree

packages/runtime-playground/src/wordpress-adversarial-adapter.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,13 @@ export function wordpressAdversarialActionSpec(action: WordPressAdversarialActio
138138
return { kind: "command", command: "wordpress.wp-cli", args: [`command=${command}`], operation: `adversarial:${action.surface}`, metadata: actionMetadata(action, capability) }
139139
}
140140
if (action.surface === "ajax") {
141+
requirePostOperation(action)
141142
if (!action.target) throw new Error("WordPress adversarial AJAX actions require the target action name.")
142-
const body = new URLSearchParams({ action: action.target, ...httpFormValues(action.input) }).toString()
143+
const body = new URLSearchParams({ ...httpFormValues(action.input), action: action.target }).toString()
143144
return { kind: "command", command: "wordpress.browser-actions", args: [`steps-json=${JSON.stringify(httpBrowserSteps("/wp-admin/admin-ajax.php", "application/x-www-form-urlencoded", body))}`], operation: "adversarial:ajax", metadata: actionMetadata(action, capability) }
144145
}
145146
if (action.surface === "xmlrpc") {
147+
requirePostOperation(action)
146148
if (typeof action.input !== "string" || action.input.trim() === "") throw new Error("WordPress adversarial XML-RPC actions require a non-empty XML request body.")
147149
return { kind: "command", command: "wordpress.browser-actions", args: [`steps-json=${JSON.stringify(httpBrowserSteps("/xmlrpc.php", "text/xml", action.input))}`], operation: "adversarial:xmlrpc", metadata: actionMetadata(action, capability) }
148150
}
@@ -324,6 +326,12 @@ function httpFormValues(value: unknown): Record<string, string> {
324326
return Object.fromEntries(Object.entries(record ?? {}).map(([name, item]) => [name, typeof item === "string" ? item : JSON.stringify(item)]))
325327
}
326328

329+
function requirePostOperation(action: WordPressAdversarialAction): void {
330+
if (action.operation.trim().toUpperCase() !== "POST") {
331+
throw new Error(`WordPress adversarial ${action.surface.toUpperCase()} actions only support POST requests.`)
332+
}
333+
}
334+
327335
function httpBrowserSteps(path: string, contentType: string, body: string): Array<Record<string, unknown>> {
328336
const expression = `const response = await fetch(${JSON.stringify(path)}, { method: 'POST', headers: { 'Content-Type': ${JSON.stringify(contentType)} }, body: ${JSON.stringify(body)} }); return response.ok`
329337
return [{ kind: "navigate", url: "/", waitFor: "load" }, { kind: "evaluate", expression, assert: true }]

tests/wordpress-adversarial-adapter.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@ const rest = wordpressAdversarialActionSpec({ surface: "rest", operation: "POST"
2626
assert.equal(rest.command, "wordpress.run-php")
2727
assert.equal(rest.metadata?.fidelity, "exact")
2828
assert.match(rest.args?.[0] ?? "", /WP_REST_Request/)
29-
const ajax = wordpressAdversarialActionSpec({ surface: "ajax", operation: "POST", target: "fixture_save", input: { value: "mutated" } })
29+
const ajax = wordpressAdversarialActionSpec({ surface: "ajax", operation: "POST", target: "fixture_save", input: { action: "overridden", value: "mutated" } })
3030
assert.equal(ajax.command, "wordpress.browser-actions")
3131
assert.match(ajax.args?.find((arg) => arg.startsWith("steps-json=")) ?? "", /action=fixture_save&value=mutated/)
32+
assert.doesNotMatch(ajax.args?.find((arg) => arg.startsWith("steps-json=")) ?? "", /overridden/)
33+
assert.throws(() => wordpressAdversarialActionSpec({ surface: "ajax", operation: "GET", target: "fixture_save" }), /only support POST/)
3234
const xmlrpc = wordpressAdversarialActionSpec({ surface: "xmlrpc", operation: "POST", input: "<methodCall><methodName>fixture.echo</methodName></methodCall>" })
3335
assert.equal(xmlrpc.command, "wordpress.browser-actions")
3436
assert.match(xmlrpc.args?.[0] ?? "", /xmlrpc\.php/)
37+
assert.throws(() => wordpressAdversarialActionSpec({ surface: "xmlrpc", operation: "PUT", input: "<methodCall />" }), /only support POST/)
3538

3639
const cli = wordpressAdversarialActionSpec({ surface: "cli", operation: "run", input: "option get home" })
3740
assert.deepEqual(cli.args, ["command=option get home"])

0 commit comments

Comments
 (0)