@openwop/openwop-conformance 1.105.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,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.
|
|
4
|
-
"corpusCommit": "
|
|
3
|
+
"suiteVersion": "1.106.1",
|
|
4
|
+
"corpusCommit": "eb487d75adb0178afdcb897e8602f6111a4099a9"
|
|
5
5
|
}
|
|
@@ -14,10 +14,15 @@
|
|
|
14
14
|
* 200 {"maxTokens":null}
|
|
15
15
|
*
|
|
16
16
|
* The registration path logged *"test seam ENABLED — NEVER enable in
|
|
17
|
-
* production"* while doing exactly that
|
|
18
|
-
* performed no tenant resolution at all — staging keyed on `nodeId`, which is
|
|
17
|
+
* production"* while doing exactly that. Staging is keyed on `nodeId`, which is
|
|
19
18
|
* not a secret: node ids ship inside chain packs.
|
|
20
19
|
*
|
|
20
|
+
* (An earlier account of this — including the first version of this docblock —
|
|
21
|
+
* said the staging route performed *no tenant resolution at all*. The reporter
|
|
22
|
+
* retracted that within the hour: auth **runs and succeeds**, minting an
|
|
23
|
+
* anonymous session. The corrected mechanism is what makes the weak prose
|
|
24
|
+
* clause interesting, so it is recorded rather than quietly swapped.)
|
|
25
|
+
*
|
|
21
26
|
* **Why this matters beyond the seam.** A staged mock program makes a replay
|
|
22
27
|
* diverge on purpose, so an unauthenticated caller could switch off the
|
|
23
28
|
* byte-equivalence `replay.md` §C.2 requires — and any host advertising
|
|
@@ -30,6 +35,16 @@
|
|
|
30
35
|
* enabled answers `404` and passes correctly, which is the honest outcome
|
|
31
36
|
* rather than a skip.
|
|
32
37
|
*
|
|
38
|
+
* **Why this leg asserts the observable property, not the mechanism.** The prose clause
|
|
39
|
+
* originally required an enabled seam to apply *"the same authentication and tenant
|
|
40
|
+
* resolution as the canonical surface"* — which RFC 0132 makes trivially satisfiable, since
|
|
41
|
+
* the canonical surface legitimately admits anonymous actors. The reporting host's auth
|
|
42
|
+
* **ran and succeeded**, minting `tenantId: "anon:<sid>"`, so the seam applied exactly the
|
|
43
|
+
* canonical treatment and the hole survived the rule. This leg reds it either way, because
|
|
44
|
+
* a `200` to a credential-less caller is the thing that matters. **The prose and this leg
|
|
45
|
+
* disagreed for twenty minutes and the leg was right** — the clause now requires a
|
|
46
|
+
* non-anonymous principal.
|
|
47
|
+
*
|
|
33
48
|
* Requires a base URL; issues NO credentials by design.
|
|
34
49
|
*/
|
|
35
50
|
|
|
@@ -52,11 +67,41 @@ const SEAM_PROBES: readonly string[] = [
|
|
|
52
67
|
/** 200 is the only unambiguous failure: the seam answered a stranger. */
|
|
53
68
|
const ACCEPTABLE = new Set([401, 403, 404, 405, 501]);
|
|
54
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
|
+
|
|
55
98
|
describe('test-seam-unauthenticated: an enabled seam still authenticates', () => {
|
|
56
99
|
it('no /v1/host/sample/* seam answers an unauthenticated request with 200', async () => {
|
|
57
100
|
const env = loadEnv();
|
|
58
101
|
const answered: string[] = [];
|
|
59
102
|
let probed = 0;
|
|
103
|
+
let seamAnswers = 0;
|
|
104
|
+
let catchAllAnswers = 0;
|
|
60
105
|
|
|
61
106
|
for (const path of SEAM_PROBES) {
|
|
62
107
|
// node:fetch directly with NO Authorization header — the driver's
|
|
@@ -68,8 +113,18 @@ describe('test-seam-unauthenticated: an enabled seam still authenticates', () =>
|
|
|
68
113
|
continue; // connection-level refusal is a stronger answer than 404
|
|
69
114
|
}
|
|
70
115
|
probed += 1;
|
|
71
|
-
if (res.status === 200)
|
|
72
|
-
|
|
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)) {
|
|
73
128
|
// Not a pass and not the known failure — record it rather than
|
|
74
129
|
// silently tolerating a status nobody reasoned about.
|
|
75
130
|
answered.push(`${path} -> ${res.status} (unexpected; expected one of ${[...ACCEPTABLE].join('/')})`);
|
|
@@ -85,16 +140,27 @@ describe('test-seam-unauthenticated: an enabled seam still authenticates', () =>
|
|
|
85
140
|
'not looking, which is the failure mode it exists to prevent',
|
|
86
141
|
).toBeGreaterThan(0);
|
|
87
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
|
+
|
|
88
161
|
expect(
|
|
89
162
|
answered,
|
|
90
|
-
driver.describe(
|
|
91
|
-
'host-sample-test-seams.md §"Production safety (normative)"',
|
|
92
|
-
'An ENABLED seam MUST apply the same authentication and tenant resolution as the canonical ' +
|
|
93
|
-
'surface. The env-gate governs whether a seam EXISTS; it does not govern who may call it. ' +
|
|
94
|
-
'A seam answering an unauthenticated request is an open control surface on a public ' +
|
|
95
|
-
'origin — and staging keys such as `nodeId` are not secrets, they ship inside chain ' +
|
|
96
|
-
'packs.\n ' + answered.join('\n '),
|
|
97
|
-
),
|
|
163
|
+
driver.describe('host-sample-test-seams.md §"Production safety (normative)"', `${diagnosis}\n ${answered.join('\n ')}`),
|
|
98
164
|
).toEqual([]);
|
|
99
165
|
});
|
|
100
166
|
});
|