@openwop/openwop-conformance 1.153.0 → 1.155.0
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 +17 -0
- package/README.md +11 -2
- package/api/openapi.yaml +17 -1
- package/coverage.md +3 -0
- package/dist/cli.js +7 -2
- package/package.json +31 -2
- package/requirements.json +256 -20
- package/schemas/CORPUS-STAMP.json +100 -3
- package/schemas/README.md +1 -0
- package/schemas/capabilities.schema.json +7 -0
- package/schemas/run-event-payloads.schema.json +9 -0
- package/schemas/run-snapshot.schema.json +4 -0
- package/schemas/subject.schema.json +56 -0
- package/src/cli.ts +7 -5
- package/src/global-setup.ts +13 -0
- package/src/lib/corpus-stamp.ts +125 -0
- package/src/scenarios/anonymous-actor-shape.test.ts +6 -1
- package/src/scenarios/auth-subject-link.test.ts +20 -1
- package/src/scenarios/discovery.test.ts +12 -0
- package/src/scenarios/identity-owner-shape.test.ts +7 -3
- package/src/scenarios/owner-subject-echo.test.ts +127 -0
- package/src/scenarios/owner-subject-shape.test.ts +96 -0
- package/src/scenarios/protocol-versions-array.test.ts +103 -0
- package/src/scenarios/webhook-signed-delivery.test.ts +22 -0
- package/src/lib/capabilities-auth-subject-link.test.ts +0 -103
- package/src/lib/fork-availability.test.ts +0 -69
- package/src/lib/global-setup.test.ts +0 -76
- package/src/lib/grpc-framing.test.ts +0 -96
- package/src/lib/oidc-issuer.test.ts +0 -328
- package/src/lib/otel-collector-grpc.test.ts +0 -191
- package/src/lib/otel-collector.test.ts +0 -303
- package/src/lib/otlp-protobuf.test.ts +0 -461
- package/src/lib/polling.test.ts +0 -80
- package/src/lib/requirement-ids.test.ts +0 -83
- package/src/lib/requirement-ledger.test.ts +0 -75
- package/src/lib/risk-disposition.test.ts +0 -91
- package/src/lib/saml-idp.test.ts +0 -127
- package/src/lib/spec-coherence-registry.test.ts +0 -155
- package/src/lib/webhook-receiver.test.ts +0 -144
- package/src/scenarios/artifact-schema-compile-bounded.test.ts +0 -126
- package/src/scenarios/artifact-type-legacy-ids.test.ts +0 -124
- package/src/scenarios/capability-example-root-layout.test.ts +0 -272
- package/src/scenarios/certification-floor-enforcement.test.ts +0 -204
- package/src/scenarios/chain-subchain-unsupported-refused.test.ts +0 -70
- package/src/scenarios/compensation-profile.test.ts +0 -340
- package/src/scenarios/core-manifest-and-extension-registry.test.ts +0 -250
- package/src/scenarios/discovery-canonical-family-no-shadow.test.ts +0 -219
- package/src/scenarios/edge-condition-truthy-falsy.test.ts +0 -108
- package/src/scenarios/effect-identity-composition.test.ts +0 -129
- package/src/scenarios/effect-identity-cross-scope.test.ts +0 -82
- package/src/scenarios/error-envelope-canonical-shape.test.ts +0 -64
- package/src/scenarios/form-content-packs.test.ts +0 -415
- package/src/scenarios/multi-region-effect-vocabulary.test.ts +0 -175
- package/src/scenarios/normative-example-extraction.test.ts +0 -242
- package/src/scenarios/openapi-asyncapi-sdk-parity.test.ts +0 -309
- package/src/scenarios/pack-manifest-extensions.test.ts +0 -203
- package/src/scenarios/protocol-version-grammar.test.ts +0 -119
- package/src/scenarios/registry-declarative-kinds.test.ts +0 -121
- package/src/scenarios/rfc-0147-self-audit.test.ts +0 -104
- package/src/scenarios/rfc-lifecycle-coherence.test.ts +0 -215
- package/src/scenarios/semantic-digest-v2.test.ts +0 -128
- package/src/scenarios/spec-corpus-validity.test.ts +0 -1727
- package/src/scenarios/spec-section-citations.test.ts +0 -132
- package/src/scenarios/tool-result-trust-monotone.test.ts +0 -168
- package/src/scenarios/versioned-composition-profiles.test.ts +0 -201
- package/src/scenarios/workflow-chain-internal-flag.test.ts +0 -84
- package/src/scenarios/workload-identity-profile.test.ts +0 -184
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RFC 0154 §A/§B — workload identity and delegation, shape only.
|
|
3
|
-
*
|
|
4
|
-
* **What this proves:** the schema admits the identity and delegation shapes §A
|
|
5
|
-
* and §B describe, and — more importantly — **rejects credential material**. It
|
|
6
|
-
* verifies no host. RFC 0147 §A.5 forbids `Accepted` on shape-only evidence for
|
|
7
|
-
* a behavioral requirement, and §A's actual requirements are behavioral:
|
|
8
|
-
* cryptographically verify the presented identity, bind it to the request,
|
|
9
|
-
* resolve it to a principal before authorization, and fail closed when
|
|
10
|
-
* unresolvable. A schema cannot demonstrate any of those.
|
|
11
|
-
*
|
|
12
|
-
* What a schema *can* do is make the dangerous shape unrepresentable, and that
|
|
13
|
-
* is what this checks. The governing rule is negative: **raw certificates,
|
|
14
|
-
* tokens, proofs, and credentials MUST NOT enter these objects.** `subject` is
|
|
15
|
-
* an opaque identifier; `proofRef` and `thumbprintRef` are digest *references*.
|
|
16
|
-
* A verified identity is a fact about a completed verification, not a container
|
|
17
|
-
* for the material that proved it — and these objects are projected into events,
|
|
18
|
-
* spans, and audit records, which is precisely where credential material must
|
|
19
|
-
* never reach.
|
|
20
|
-
*
|
|
21
|
-
* The second rule the schema encodes is **identity is not authorization**
|
|
22
|
-
* (RFC 0147 R12). Knowing which workload called says nothing about what it may
|
|
23
|
-
* do. That one cannot be schema-enforced at all, which is why `audience` is
|
|
24
|
-
* required on a delegation: an identity minted for somewhere else is the
|
|
25
|
-
* confused-deputy path, and the audience check is what closes it.
|
|
26
|
-
*
|
|
27
|
-
* Server-free.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
import { describe, it, expect } from 'vitest';
|
|
31
|
-
import { readFileSync } from 'node:fs';
|
|
32
|
-
import { join, resolve as pathResolve } from 'node:path';
|
|
33
|
-
import Ajv2020 from 'ajv/dist/2020.js';
|
|
34
|
-
import addFormats from 'ajv-formats';
|
|
35
|
-
import { SCHEMAS_DIR, V1_DIR } from '../lib/paths.js';
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Schemas ship inside the package; RFC prose does not. The two need different
|
|
39
|
-
* treatment, and collapsing them onto one `ROOT` derived from `V1_DIR` is what
|
|
40
|
-
* broke this file in the published layout — `V1_DIR` is null there, and the
|
|
41
|
-
* `as string` cast carried the null straight into `join()`.
|
|
42
|
-
*/
|
|
43
|
-
const RFCS_DIR = V1_DIR === null ? null : pathResolve(V1_DIR, '..', '..', 'RFCS');
|
|
44
|
-
|
|
45
|
-
function validator() {
|
|
46
|
-
const schema = JSON.parse(
|
|
47
|
-
readFileSync(join(SCHEMAS_DIR, 'workload-identity.schema.json'), 'utf8'),
|
|
48
|
-
) as object;
|
|
49
|
-
const ajv = new Ajv2020({ strict: false, allErrors: true });
|
|
50
|
-
addFormats(ajv);
|
|
51
|
-
return ajv.compile(schema);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const DIGEST = `sha256:${'a'.repeat(64)}`;
|
|
55
|
-
|
|
56
|
-
describe('RFC 0154 §A — workload identity shape', () => {
|
|
57
|
-
const validate = validator();
|
|
58
|
-
|
|
59
|
-
it('a verified identity validates', () => {
|
|
60
|
-
expect(
|
|
61
|
-
validate({ scheme: 'spiffe', subject: 'spiffe://example/dispatcher', issuer: 'spiffe://example', audience: 'openwop-host' }),
|
|
62
|
-
JSON.stringify(validate.errors),
|
|
63
|
-
).toBe(true);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('the object is closed — credential material cannot be smuggled in', () => {
|
|
67
|
-
// The governing rule, and the one a schema can actually enforce. These
|
|
68
|
-
// objects reach events, spans, and audit records; an open object is an
|
|
69
|
-
// invitation for a token to ride along into all three.
|
|
70
|
-
for (const extra of [
|
|
71
|
-
{ certificate: '-----BEGIN CERTIFICATE-----' },
|
|
72
|
-
{ token: 'eyJhbGciOiJIUzI1NiJ9.x.y' },
|
|
73
|
-
{ privateKey: 'secret' },
|
|
74
|
-
{ proof: 'raw-proof-bytes' },
|
|
75
|
-
]) {
|
|
76
|
-
expect(
|
|
77
|
-
validate({ scheme: 'spiffe', subject: 'spiffe://example/x', ...extra }),
|
|
78
|
-
`RFC 0154 §A: raw credentials MUST NOT enter the identity object — rejected \`${Object.keys(extra)[0]}\``,
|
|
79
|
-
).toBe(false);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('the scheme is a closed enum', () => {
|
|
84
|
-
expect(
|
|
85
|
-
validate({ scheme: 'trust-me', subject: 'x' }),
|
|
86
|
-
'RFC 0154 §A: an unrecognized scheme is a verification path nobody implemented. Accepting ' +
|
|
87
|
-
'the NAME without the verification is exactly the failure this profile prevents.',
|
|
88
|
-
).toBe(false);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('subject is required and non-empty', () => {
|
|
92
|
-
expect(validate({ scheme: 'spiffe' })).toBe(false);
|
|
93
|
-
expect(validate({ scheme: 'spiffe', subject: '' })).toBe(false);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('a key binding carries a digest reference, never a key', () => {
|
|
97
|
-
expect(validate({ scheme: 'mtls-san', subject: 'x', keyBinding: { method: 'mtls', thumbprintRef: DIGEST } })).toBe(true);
|
|
98
|
-
expect(
|
|
99
|
-
validate({ scheme: 'mtls-san', subject: 'x', keyBinding: { method: 'mtls', thumbprintRef: 'MIIBIjANBgkqh' } }),
|
|
100
|
-
'RFC 0154 §A: `thumbprintRef` is pattern-constrained to a sha256 digest so a raw key cannot ' +
|
|
101
|
-
'be pasted here and still validate.',
|
|
102
|
-
).toBe(false);
|
|
103
|
-
expect(validate({ scheme: 'mtls-san', subject: 'x', keyBinding: { method: 'telepathy' } })).toBe(false);
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe('RFC 0154 §B — delegated actor chain', () => {
|
|
108
|
-
const validate = validator();
|
|
109
|
-
const base = { scheme: 'spiffe' as const, subject: 'spiffe://example/planner' };
|
|
110
|
-
|
|
111
|
-
it('a verified delegation validates', () => {
|
|
112
|
-
expect(
|
|
113
|
-
validate({
|
|
114
|
-
...base,
|
|
115
|
-
delegation: {
|
|
116
|
-
chain: [{ subject: 'spiffe://example/dispatcher', issuer: 'spiffe://example' }],
|
|
117
|
-
audience: 'openwop-host',
|
|
118
|
-
expiresAt: '2026-08-13T16:00:00Z',
|
|
119
|
-
proofRef: DIGEST,
|
|
120
|
-
},
|
|
121
|
-
}),
|
|
122
|
-
JSON.stringify(validate.errors),
|
|
123
|
-
).toBe(true);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('audience is required on a delegation', () => {
|
|
127
|
-
// The confused-deputy guard (RFC 0147 R12). An identity minted for somewhere
|
|
128
|
-
// else, accepted here, is how a credential valid elsewhere becomes a
|
|
129
|
-
// credential valid here.
|
|
130
|
-
expect(
|
|
131
|
-
validate({ ...base, delegation: { chain: [{ subject: 'x' }] } }),
|
|
132
|
-
'RFC 0154 §B: a delegation without an audience cannot be checked against this host.',
|
|
133
|
-
).toBe(false);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('an empty chain is rejected', () => {
|
|
137
|
-
expect(
|
|
138
|
-
validate({ ...base, delegation: { chain: [], audience: 'openwop-host' } }),
|
|
139
|
-
'RFC 0154 §B: a delegation context with no chain claims a delegation nobody can inspect.',
|
|
140
|
-
).toBe(false);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('proofRef is a digest reference, not the proof', () => {
|
|
144
|
-
expect(
|
|
145
|
-
validate({ ...base, delegation: { chain: [{ subject: 'x' }], audience: 'h', proofRef: 'eyJhbGciOiJIUzI1NiJ9.a.b' } }),
|
|
146
|
-
'RFC 0154 §B: `proofRef` is a REFERENCE. A JWT pasted here would put the proof itself into ' +
|
|
147
|
-
'every event and span this object reaches.',
|
|
148
|
-
).toBe(false);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('a chain hop cannot carry extra fields', () => {
|
|
152
|
-
expect(validate({ ...base, delegation: { chain: [{ subject: 'x', token: 'y' }], audience: 'h' } })).toBe(false);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('a chain hop MAY carry its verified scopes, as a set of non-empty strings', () => {
|
|
156
|
-
// RFC 0154 §B "Bounds": scope amplification is only observable if a hop can
|
|
157
|
-
// state the scopes its proof carried. Provenance, not authorization.
|
|
158
|
-
expect(validate({ ...base, delegation: { chain: [{ subject: 'x', scopes: ['runs:read'] }, { subject: 'y', scopes: ['runs:read'] }], audience: 'h' } })).toBe(true);
|
|
159
|
-
expect(validate({ ...base, delegation: { chain: [{ subject: 'x', scopes: 'runs:read' }], audience: 'h' } })).toBe(false);
|
|
160
|
-
expect(validate({ ...base, delegation: { chain: [{ subject: 'x', scopes: ['runs:read', 'runs:read'] }], audience: 'h' } })).toBe(false);
|
|
161
|
-
expect(validate({ ...base, delegation: { chain: [{ subject: 'x', scopes: [''] }], audience: 'h' } })).toBe(false);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
describe.skipIf(RFCS_DIR === null)('RFC 0154 — what this does NOT establish', () => {
|
|
166
|
-
it('the behavioral and external-audit items stay unticked and annotated', () => {
|
|
167
|
-
// §A's real requirements are behavioral — verify, bind, resolve, fail closed
|
|
168
|
-
// — and none is schema-checkable. §A.5 forbids `Accepted` on this evidence.
|
|
169
|
-
const rfc = readFileSync(
|
|
170
|
-
join(RFCS_DIR as string, '0154-workload-identity-delegation-telemetry-and-provenance.md'),
|
|
171
|
-
'utf8',
|
|
172
|
-
);
|
|
173
|
-
for (const needle of [
|
|
174
|
-
'Verification, fail-closed authorization, sender constraint, and negative chain tests pass.',
|
|
175
|
-
'External auditor reviews the identity and provenance implementation.',
|
|
176
|
-
]) {
|
|
177
|
-
expect(
|
|
178
|
-
new RegExp(`- \\[ \\] ${needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*\\(`).test(rfc),
|
|
179
|
-
`RFC 0154: "${needle}" MUST remain unticked and annotated — a schema cannot demonstrate ` +
|
|
180
|
-
'cryptographic verification, and no host implements this profile.',
|
|
181
|
-
).toBe(true);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
});
|