@openwop/openwop-conformance 1.18.0 → 1.18.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/CHANGELOG.md +8 -0
- package/package.json +1 -1
- package/src/scenarios/auth-saml-profile.test.ts +51 -14
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
_No unreleased changes._
|
|
6
6
|
|
|
7
|
+
## [1.18.1] — 2026-06-01 — RFC 0050 SAML behavioral leg: full 7-variant seam coverage
|
|
8
|
+
|
|
9
|
+
Standalone conformance patch — published via the `openwop-conformance/v1.18.1` per-package tag (PUBLISHING.md §"CI automation"; only the `publish-conformance` job runs), NOT a coordinated spec-corpus release. `EXPECTED_CONFORMANCE_VERSION` advances to `1.18.1` in lockstep. No new scenario file → patch bump.
|
|
10
|
+
|
|
11
|
+
### Changed — RFC 0050 `auth-saml-profile` opt-in behavioral leg
|
|
12
|
+
|
|
13
|
+
Expanded the opt-in `auth/saml/validate` behavioral leg from a single `alg-none` negative to the **full RFC 0050 §A variant set driven over the host's live seam**: 1 positive (valid, signed, in-window, non-wrapped → MUST be accepted, 2xx) + 6 negatives (`alg-none`, `unsigned`, `bad-signature`, `expired`, `not-yet-valid`, `signature-wrapping` → MUST be rejected, non-2xx). The signature-wrapping (XSW) case is the load-bearing security property. This makes the behavioral cert **non-vacuous against a host's real XML-DSig ACS** (distinct from the in-process reference suite, which proves the assertions detectably malformed against the bundled oracle, not the host). Still gated on `OPENWOP_TEST_SAML_IDP_URL` (an operator-supplied HTTP synthetic IdP serving the bundled `createSyntheticSamlIdp()` assertions) + the `openwop-auth-saml` advertisement; soft-skips otherwise. The steward prerequisite for graduating RFC 0050 `Active → Accepted` on a host with a real SAML ACS. Scenario docstring updated `Draft → Active`.
|
|
14
|
+
|
|
7
15
|
## [1.18.0] — 2026-06-01 — RFC 0085 openwop-agent-platform live aggregate-evidence gate
|
|
8
16
|
|
|
9
17
|
Standalone conformance minor — a scenario addition published via the `openwop-conformance/v1.18.0` per-package tag (PUBLISHING.md §"CI automation"; only the `publish-conformance` job runs), NOT a coordinated spec-corpus release. `EXPECTED_CONFORMANCE_VERSION` advances to `1.18.0` in lockstep. The steward prerequisite that lets MyndHyve run the RFC 0085 §C live aggregate-evidence scenario non-vacuously under `OPENWOP_REQUIRE_BEHAVIOR=true` to graduate the `openwop-agent-platform` meta-profile from `Active` to `Accepted` — the capstone of the agent-platform program. The normative surface (the `nondeterminismPolicy.declared` flag + the `isAgentPlatform*` predicate helpers + the operational annex) already shipped — this release is the gated test surface only.
|
package/package.json
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* auth-saml-profile — RFC 0050: openwop-auth-saml profile.
|
|
3
3
|
*
|
|
4
|
-
* Status:
|
|
5
|
-
* `
|
|
4
|
+
* Status: ACTIVE. RFC 0050 (SAML / SCIM enterprise identity profiles) is
|
|
5
|
+
* `Active`. The profile is documented in `auth-profiles.md`
|
|
6
6
|
* §`openwop-auth-saml` and reserved in `capabilities.auth.profiles`.
|
|
7
7
|
*
|
|
8
8
|
* Capability shape runs unconditionally when the profile is advertised.
|
|
9
|
-
* The assertion-validation behavior
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `
|
|
13
|
-
*
|
|
9
|
+
* The assertion-validation behavior is exercised over the host's live
|
|
10
|
+
* `auth/saml/validate` seam for the FULL §A variant set — 1 positive
|
|
11
|
+
* (valid, signed, in-window, non-wrapped → accepted) + 6 negatives
|
|
12
|
+
* (`alg:none`, unsigned, bad-signature, `NotOnOrAfter` expiry, `NotBefore`
|
|
13
|
+
* not-yet-valid, signature-wrapping → rejected). This behavioral leg is
|
|
14
|
+
* opt-in via `OPENWOP_TEST_SAML_IDP_URL` (an operator-supplied HTTP
|
|
15
|
+
* synthetic IdP serving the bundled `createSyntheticSamlIdp()` assertions),
|
|
16
|
+
* because it requires a live host ACS + an HTTP IdP the seam can resolve
|
|
17
|
+
* variants from — the in-process minter below is not a server. Follows the
|
|
14
18
|
* `auth-mtls.test.ts` opt-in precedent. Soft-skips otherwise.
|
|
15
19
|
*
|
|
16
20
|
* @see RFCS/0050-saml-scim-enterprise-identity-profiles.md
|
|
@@ -65,19 +69,52 @@ describe('auth-saml-profile: advertisement shape (RFC 0050)', () => {
|
|
|
65
69
|
describe('auth-saml-profile: assertion validation (RFC 0050 §A — opt-in)', () => {
|
|
66
70
|
const idpUrl = process.env.OPENWOP_TEST_SAML_IDP_URL;
|
|
67
71
|
|
|
68
|
-
|
|
72
|
+
// The host's real SAML ACS is driven over the `auth/saml/validate` seam for
|
|
73
|
+
// every variant the bundled synthetic IdP mints — the full RFC 0050 §A MUST
|
|
74
|
+
// list, not just one negative. The seam receives `{ idpUrl, variant }`,
|
|
75
|
+
// resolves `{ certificatePem, assertion }` of that variant from the
|
|
76
|
+
// operator-supplied synthetic IdP, runs it through the host's genuine
|
|
77
|
+
// validator, and answers 2xx (accepted) / non-2xx (rejected). This is the
|
|
78
|
+
// NON-VACUOUS behavioral leg: it exercises the host's ACS on the live wire,
|
|
79
|
+
// distinct from the in-process reference suite below (which proves the
|
|
80
|
+
// assertions are detectably malformed against the bundled oracle, not the
|
|
81
|
+
// host). All legs soft-skip until `OPENWOP_TEST_SAML_IDP_URL` is supplied.
|
|
82
|
+
const gated = (): boolean => idpUrl !== undefined && idpUrl.length > 0;
|
|
83
|
+
|
|
84
|
+
it('ACCEPTS a valid signed, in-window, non-wrapped assertion (synthetic IdP required)', async () => {
|
|
69
85
|
const profiles = await readProfiles();
|
|
70
86
|
if (profiles === null || !profiles.includes(SAML_PROFILE)) return; // capability-gated
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
// host's SAML ACS MUST be rejected with `unauthenticated`.
|
|
74
|
-
const res = await driver.post('/v1/host/sample/auth/saml/validate', { idpUrl, variant: 'alg-none' });
|
|
87
|
+
if (!gated()) return; // opt-in: synthetic-IdP harness not provided
|
|
88
|
+
const res = await driver.post('/v1/host/sample/auth/saml/validate', { idpUrl, variant: 'valid' });
|
|
75
89
|
if (res.status === 404) return; // seam unwired
|
|
76
90
|
expect(
|
|
77
91
|
res.status,
|
|
78
|
-
driver.describe('RFC 0050 §A', '
|
|
79
|
-
).
|
|
92
|
+
driver.describe('RFC 0050 §A', 'a valid signed, in-window, non-wrapped SAML assertion MUST be accepted (2xx)'),
|
|
93
|
+
).toBeLessThan(400);
|
|
80
94
|
});
|
|
95
|
+
|
|
96
|
+
// The 6 negatives the RFC 0050 §A MUST list requires a host to reject. The
|
|
97
|
+
// signature-wrapping (XSW) case is the load-bearing security property — a
|
|
98
|
+
// host MUST bind the validated signature to the consumed assertion.
|
|
99
|
+
const negatives: ReadonlyArray<[Exclude<SamlVariant, 'valid'>, string]> = [
|
|
100
|
+
['alg-none', 'an `alg:none` SAML assertion MUST be rejected (non-2xx)'],
|
|
101
|
+
['unsigned', 'an unsigned SAML assertion MUST be rejected (non-2xx)'],
|
|
102
|
+
['bad-signature', 'a SAML assertion with an invalid signature MUST be rejected (non-2xx)'],
|
|
103
|
+
['expired', 'a SAML assertion past `NotOnOrAfter` MUST be rejected (non-2xx)'],
|
|
104
|
+
['not-yet-valid', 'a SAML assertion before `NotBefore` MUST be rejected (non-2xx)'],
|
|
105
|
+
['signature-wrapping', 'a signature-wrapped (XSW) SAML assertion MUST be rejected (non-2xx)'],
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
for (const [variant, requirement] of negatives) {
|
|
109
|
+
it(`REJECTS the ${variant} assertion over the seam (synthetic IdP required)`, async () => {
|
|
110
|
+
const profiles = await readProfiles();
|
|
111
|
+
if (profiles === null || !profiles.includes(SAML_PROFILE)) return; // capability-gated
|
|
112
|
+
if (!gated()) return; // opt-in: synthetic-IdP harness not provided
|
|
113
|
+
const res = await driver.post('/v1/host/sample/auth/saml/validate', { idpUrl, variant });
|
|
114
|
+
if (res.status === 404) return; // seam unwired
|
|
115
|
+
expect(res.status, driver.describe('RFC 0050 §A', requirement)).toBeGreaterThanOrEqual(400);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
81
118
|
});
|
|
82
119
|
|
|
83
120
|
describe('category: auth-saml synthetic-IdP reference suite (RFC 0050 §A)', () => {
|