@openwop/openwop-conformance 1.68.0 → 1.68.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -135,3 +135,26 @@ export function signPayload(
|
|
|
135
135
|
algorithmHeader: 'v1',
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Discover a tenant the calling bearer provably OWNS, by creating a probe run
|
|
141
|
+
* and reading `owner.tenantId` off its snapshot (RFC 0048). A host that scopes
|
|
142
|
+
* webhook subscriptions by tenant membership (RFC 0093) 403s a `tenantId` the
|
|
143
|
+
* bearer is not a member of — so a hard-coded tenant is wrong; the only portable
|
|
144
|
+
* owned tenant is the one on a run the bearer just created. Returns `undefined`
|
|
145
|
+
* for a single-tenant host (which omits `owner.tenantId`) or when no probe
|
|
146
|
+
* fixture is available — callers then omit `tenantId`, which single-tenant hosts
|
|
147
|
+
* accept. (RFC 0093 / webhooks.md §Register; suite defect fixed 2026-08-09.)
|
|
148
|
+
*/
|
|
149
|
+
export async function discoverOwnedTenant(
|
|
150
|
+
driver: { post: (p: string, b: unknown) => Promise<{ status: number; json: unknown }>; get: (p: string) => Promise<{ status: number; json: unknown }> },
|
|
151
|
+
probeWorkflowId = 'conformance-noop',
|
|
152
|
+
): Promise<string | undefined> {
|
|
153
|
+
const create = await driver.post('/v1/runs', { workflowId: probeWorkflowId });
|
|
154
|
+
if (create.status !== 201) return undefined;
|
|
155
|
+
const runId = (create.json as { runId?: string } | null)?.runId;
|
|
156
|
+
if (typeof runId !== 'string') return undefined;
|
|
157
|
+
const snap = await driver.get(`/v1/runs/${encodeURIComponent(runId)}`);
|
|
158
|
+
const owner = (snap.json as { owner?: { tenantId?: unknown } } | null)?.owner;
|
|
159
|
+
return typeof owner?.tenantId === 'string' ? owner.tenantId : undefined;
|
|
160
|
+
}
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import { describe, it, expect } from 'vitest';
|
|
24
24
|
import { driver } from '../lib/driver.js';
|
|
25
|
+
import { discoverOwnedTenant } from '../lib/webhook-receiver.js';
|
|
25
26
|
|
|
26
27
|
async function isWebhookSupported(): Promise<boolean> {
|
|
27
28
|
const disco = await driver.get('/.well-known/openwop');
|
|
@@ -40,10 +41,11 @@ describe('webhook-negative: SSRF guard rejects private destinations', () => {
|
|
|
40
41
|
// Spec-complete except for the private URL, so validation passes and the
|
|
41
42
|
// request reaches the SSRF guard. `{ url }` alone 400s `validation_error`
|
|
42
43
|
// (missing `events`) before the guard runs. (Suite defect, fixed 2026-08-09.)
|
|
44
|
+
const ownedTenant = await discoverOwnedTenant(driver);
|
|
43
45
|
const reg = await driver.post('/v1/webhooks', {
|
|
44
46
|
url: 'http://127.0.0.1:65535/',
|
|
45
47
|
events: ['run.completed'],
|
|
46
|
-
tenantId:
|
|
48
|
+
...(ownedTenant ? { tenantId: ownedTenant } : {}),
|
|
47
49
|
});
|
|
48
50
|
if (reg.status === 201) {
|
|
49
51
|
// Host accepted — SSRF guard not implemented or bypassed.
|
|
@@ -29,6 +29,7 @@ import { createServer, type IncomingMessage, type Server, type ServerResponse }
|
|
|
29
29
|
import { driver } from '../lib/driver.js';
|
|
30
30
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
31
31
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
32
|
+
import { discoverOwnedTenant } from '../lib/webhook-receiver.js';
|
|
32
33
|
|
|
33
34
|
interface DeliveredRequest {
|
|
34
35
|
readonly headers: Record<string, string>;
|
|
@@ -93,10 +94,14 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
|
|
|
93
94
|
// The pre-fix `{ url }`-only body 400s on validation before delivery can occur.
|
|
94
95
|
// (Suite defect, fixed 2026-08-09.) The delivered run's tenant must match this
|
|
95
96
|
// subscription's tenantId — verified end-to-end against the openwop-app host.
|
|
97
|
+
// Derive a tenant the bearer OWNS from a probe run's snapshot — a hard-coded
|
|
98
|
+
// tenantId is 403'd by a host that scopes subscriptions by membership
|
|
99
|
+
// (RFC 0093). Single-tenant hosts return undefined ⇒ omit tenantId.
|
|
100
|
+
const ownedTenant = await discoverOwnedTenant(driver);
|
|
96
101
|
const reg = await driver.post('/v1/webhooks', {
|
|
97
102
|
url: receiver.url,
|
|
98
103
|
events: ['run.completed'],
|
|
99
|
-
tenantId:
|
|
104
|
+
...(ownedTenant ? { tenantId: ownedTenant } : {}),
|
|
100
105
|
});
|
|
101
106
|
|
|
102
107
|
// SSRF guard skip: if the host rejects loopback destinations,
|