@openwop/openwop-conformance 1.106.0 → 1.106.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "1.106.0",
3
+ "version": "1.106.1",
4
4
  "description": "Production-ready black-box conformance suite for OpenWOP v1.0 compliant servers.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "_comment": "Provenance of this vendored schemas/ copy. See conformance/README.md \u00a7\"Resolving the contract\". Compare against the stamp in your installed @openwop/openwop-conformance to detect a stale hand-copied contract.",
3
- "suiteVersion": "1.106.0",
4
- "corpusCommit": "328f41b272bce308f9d6c3c09209b69b1f0b118e"
3
+ "suiteVersion": "1.106.1",
4
+ "corpusCommit": "eb487d75adb0178afdcb897e8602f6111a4099a9"
5
5
  }
@@ -67,11 +67,41 @@ const SEAM_PROBES: readonly string[] = [
67
67
  /** 200 is the only unambiguous failure: the seam answered a stranger. */
68
68
  const ACCEPTABLE = new Set([401, 403, 404, 405, 501]);
69
69
 
70
+ /**
71
+ * A `200` can arrive from two very different places, and the difference decides
72
+ * what you should go fix.
73
+ *
74
+ * Every seam in `host-sample-test-seams.md` answers with a JSON body — the doc
75
+ * specifies each one's response shape as an object (`{ released, event }`,
76
+ * `{ ok: true, packId }`, `{ entries: … }`). **No seam answers with an HTML
77
+ * document.** So a `200 text/html` is not a seam that talked to a stranger; it
78
+ * is a static-hosting catch-all — an SPA rewrite in front of the API — matching
79
+ * a path the backend never saw.
80
+ *
81
+ * This was found by probing `app.openwop.dev` (Firebase Hosting, `**` rewrite
82
+ * to the SPA shell) instead of the backend's own origin. Every probe returned
83
+ * `200 text/html` with the app's `index.html`, which under the previous message
84
+ * read as *"an open control surface on a public origin"* — alarming, and wrong.
85
+ * The same paths on `…-backend-….run.app` return `404 application/json`.
86
+ *
87
+ * **Both cases still fail.** Nothing here relaxes the assertion, because the
88
+ * cheap way to silence a false alarm is to stop failing on `200`, and that would
89
+ * delete the check. What changes is the diagnosis: an HTML catch-all means the
90
+ * suite is pointed at the CDN rather than the API origin, which is its own
91
+ * defect — a run against the wrong origin cannot witness anything about the
92
+ * host, and every other leg in the suite is equally blind at that base URL.
93
+ */
94
+ function classify200(contentType: string | null): 'seam' | 'catchall' {
95
+ return (contentType ?? '').toLowerCase().includes('text/html') ? 'catchall' : 'seam';
96
+ }
97
+
70
98
  describe('test-seam-unauthenticated: an enabled seam still authenticates', () => {
71
99
  it('no /v1/host/sample/* seam answers an unauthenticated request with 200', async () => {
72
100
  const env = loadEnv();
73
101
  const answered: string[] = [];
74
102
  let probed = 0;
103
+ let seamAnswers = 0;
104
+ let catchAllAnswers = 0;
75
105
 
76
106
  for (const path of SEAM_PROBES) {
77
107
  // node:fetch directly with NO Authorization header — the driver's
@@ -83,8 +113,18 @@ describe('test-seam-unauthenticated: an enabled seam still authenticates', () =>
83
113
  continue; // connection-level refusal is a stronger answer than 404
84
114
  }
85
115
  probed += 1;
86
- if (res.status === 200) answered.push(`${path} -> 200`);
87
- else if (!ACCEPTABLE.has(res.status)) {
116
+ if (res.status === 200) {
117
+ const contentType = res.headers.get('content-type');
118
+ const kind = classify200(contentType);
119
+ if (kind === 'seam') seamAnswers += 1;
120
+ else catchAllAnswers += 1;
121
+ answered.push(
122
+ `${path} -> 200 (${contentType ?? 'no content-type'}) — ` +
123
+ (kind === 'seam'
124
+ ? 'SEAM ANSWERED A CREDENTIAL-LESS CALLER'
125
+ : 'HTML body: a static-hosting catch-all, not the API origin'),
126
+ );
127
+ } else if (!ACCEPTABLE.has(res.status)) {
88
128
  // Not a pass and not the known failure — record it rather than
89
129
  // silently tolerating a status nobody reasoned about.
90
130
  answered.push(`${path} -> ${res.status} (unexpected; expected one of ${[...ACCEPTABLE].join('/')})`);
@@ -100,17 +140,27 @@ describe('test-seam-unauthenticated: an enabled seam still authenticates', () =>
100
140
  'not looking, which is the failure mode it exists to prevent',
101
141
  ).toBeGreaterThan(0);
102
142
 
143
+ // The diagnosis is chosen from what the answers actually were. An HTML
144
+ // catch-all and an open seam are both failures and they are not the same
145
+ // bug, so the message must not name the wrong one — a check that reports a
146
+ // security finding for a misrouted base URL trains its reader to distrust it.
147
+ const diagnosis =
148
+ seamAnswers === 0 && catchAllAnswers > 0
149
+ ? 'WRONG ORIGIN, not an open seam. Every 200 above carried an HTML body, and no seam in ' +
150
+ 'host-sample-test-seams.md answers with HTML — so these are a static-hosting rewrite ' +
151
+ '(an SPA `**` catch-all) matching paths the backend never received. Point ' +
152
+ 'OPENWOP_BASE_URL at the API origin itself. This is still a failure: at this base URL ' +
153
+ 'no leg in the suite is witnessing the host, so a green run here would mean nothing.'
154
+ : 'An ENABLED seam MUST require an authenticated, NON-ANONYMOUS principal. A host that ' +
155
+ 'mints an anonymous identity for credential-less callers MUST NOT treat it as ' +
156
+ 'satisfying that. The env-gate governs whether a seam EXISTS; it does not govern who ' +
157
+ 'may call it. A seam answering a credential-less request with 200 is an open control ' +
158
+ 'surface on a public origin — and staging keys such as `nodeId` are not secrets, they ' +
159
+ 'ship inside chain packs.';
160
+
103
161
  expect(
104
162
  answered,
105
- driver.describe(
106
- 'host-sample-test-seams.md §"Production safety (normative)"',
107
- 'An ENABLED seam MUST require an authenticated, NON-ANONYMOUS principal. A host that mints ' +
108
- 'an anonymous identity for credential-less callers MUST NOT treat it as satisfying that. ' +
109
- 'The env-gate governs whether a seam EXISTS; it does not govern who may call it. A seam ' +
110
- 'answering a credential-less request with 200 is an open control surface on a public ' +
111
- 'origin — and staging keys such as `nodeId` are not secrets, they ship inside chain ' +
112
- 'packs.\n ' + answered.join('\n '),
113
- ),
163
+ driver.describe('host-sample-test-seams.md §"Production safety (normative)"', `${diagnosis}\n ${answered.join('\n ')}`),
114
164
  ).toEqual([]);
115
165
  });
116
166
  });