@agent-ix/spec-objects-security 0.4.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/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # spec-objects-security
2
+
3
+ > Filament Module: tier-2 security and identity ObjectTypes (threats, controls, auth flows, secrets, policies)
4
+
5
+ `spec-objects-security` is an **Agent-IX Filament module**: a `manifest.yaml` plus per-kind authoring **skeletons** (and frontmatter schemas) that teach the spec tooling a vocabulary of object kinds. It is not a standalone app — it is loaded by [`quire-cli`](https://github.com/agent-ix/quire-cli) and [`quoin`](https://github.com/agent-ix/quoin) to author and validate Markdown spec artifacts.
6
+
7
+ ## Installing quire-cli
8
+
9
+ `@agent-ix` packages are published to public npm. Install the CLI globally:
10
+
11
+ ```bash
12
+ npm install -g @agent-ix/quire-cli
13
+ ```
14
+
15
+ See https://github.com/agent-ix/quire-cli#install for details.
16
+
17
+ ## Install this module via npm
18
+
19
+ This module is also published as a config-only npm package: `@agent-ix/spec-objects-security`.
20
+ The package root **is** the Filament module (`manifest.yaml` + schemas/skeletons),
21
+ so it works directly as a `--module` target or via quoin's `package:` source.
22
+
23
+ ```bash
24
+ npm install @agent-ix/spec-objects-security
25
+ ```
26
+
27
+ ```bash
28
+ # quoin — resolve the module from npm by name
29
+ quoin plugin install package:@agent-ix/spec-objects-security
30
+
31
+ # or point any tool at the installed package root
32
+ quire validate spec/**/*.md --module node_modules/@agent-ix/spec-objects-security
33
+ ```
34
+
35
+ ## Object types provided
36
+
37
+ This module adds the following object kinds (frontmatter `type:` value in parentheses). All kinds are tier-2 embedded ObjectTypes — they require `id`, `title` and `type` in frontmatter plus the kind-specific contract below.
38
+
39
+ | Object | `type:` | Description |
40
+ |--------|---------|-------------|
41
+ | Auth flow | `auth_flow` | An authentication/login flow, with a `Flow` section carrying a mermaid diagram of the sequence (e.g. OIDC authorization code flow). |
42
+ | Permission | `permission` | A single grantable permission, naming the protected `resource` kind and the `verb` (action) it grants on it. |
43
+ | Scope | `scope` | An OAuth/authorization scope, with a `Grants` section listing the permissions the scope confers. |
44
+ | Role | `role` | A named role, with a `Permissions` section listing the permissions bound to it. |
45
+ | Secret | `secret` | A managed secret (e.g. client secret), with a `Rotation` section describing how and how often it rotates. |
46
+ | Encryption key | `encryption_key` | An encryption key, with frontmatter `algorithm` (cipher) and optional `rotation` cadence. |
47
+ | Session config | `session_config` | A session configuration, with a `Settings` section enumerating the effective session parameters (cookies, timeouts, limits). |
48
+ | Data classification | `data_classification` | A data sensitivity class, with a `Handling` section stating the handling requirements for that class. |
49
+ | Trust boundary | `trust_boundary` | A trust boundary between security zones, with a `Boundary` section carrying a mermaid diagram of the zones. |
50
+ | Audit event | `audit_event` | An emitted audit/log event, with an `Event Schema` section carrying a JSON code block of its payload schema. |
51
+ | CSRF token | `csrf_token` | A CSRF-protection token, with frontmatter `rotation_window` (how often a fresh token is issued). |
52
+ | CORS policy | `cors_policy` | A browser cross-origin policy, with an `Origins` section enumerating the allowed origins and their rules. |
53
+ | Password policy | `password_policy` | A password policy, with a `Rules` section enumerating the enforced password rules. |
54
+ | MFA method | `mfa_method` | A multi-factor authentication method, with frontmatter `kind` (method kind) and optional `factor` (auth factor class). |
55
+ | JWT claim | `jwt_claim` | A JWT/token claim, with a `Schema` section carrying a JSON code block of the claim's JSON Schema. |
56
+ | Threat | `threat` | A threat, with frontmatter `stride_category` (STRIDE class) and `vector` (the attack vector). |
57
+ | Control | `control` | A security control, with a `Mappings` section mapping it to the threats, risks and standards it addresses. |
58
+ | Risk | `risk` | A risk-register entry, with frontmatter `likelihood` and `impact` qualitative ratings driving its score. |
59
+ | Vulnerability | `vulnerability` | A vulnerability, with frontmatter `severity` and optional `cve_id` linking the public advisory. |
60
+ | Asset | `asset` | A protected asset, with a `Description` section describing the asset and why it matters. |
61
+ | Attack surface | `attack_surface` | An attack surface, with an `Entry Points` section enumerating the externally reachable entry points. |
62
+ | Policy | `policy` | A normative security policy, with a `Policy` section stating the policy text. |
63
+ | Audit finding | `audit_finding` | A security-audit finding, with a `Recommendation` section stating the remediation recommendation. |
64
+
65
+ ## How this module is used
66
+
67
+ ### With quoin (recommended)
68
+
69
+ [`quoin`](https://github.com/agent-ix/quoin) is the spec-domain authoring CLI. Install the module as a plugin, then author and validate:
70
+
71
+ ```bash
72
+ quoin plugin install path:../spec-objects-security
73
+ quoin catalog list # list the kinds this module adds
74
+ quoin write . --types threat,control # scaffold artifacts of these kinds
75
+ quoin review # validate authored files
76
+ ```
77
+
78
+ ### With quire-cli directly
79
+
80
+ Point `quire` at this module (the package directory holds `manifest.yaml` and `skeletons/`):
81
+
82
+ ```bash
83
+ quire schema threat --module ./spec_objects_security # a kind's extraction contract
84
+ quire validate spec/**/*.md --module ./spec_objects_security # structural validation
85
+ quire extract <DOC> --module ./spec_objects_security # extract structured data
86
+ ```
87
+
88
+ See the [quire-cli usage docs](https://github.com/agent-ix/quire-cli#usage-instructions) for all commands.
89
+
90
+ ## Development
91
+
92
+ This module is a flat-layout Python package (`spec_objects_security`, Python 3.13+) managed with [Poetry](https://python-poetry.org/) and built/published via GitHub Actions to Google Artifact Registry (PyPI-compatible).
93
+
94
+ ```bash
95
+ make install # install dependencies in the Poetry venv
96
+ make test # run pytest
97
+ make lint # ruff + black --check
98
+ make format # auto-format (ruff --fix + black)
99
+ make build # build wheel + sdist under dist/
100
+ make local-publish # publish to the local pypi.ix registry
101
+ make update-lock # update poetry.lock
102
+ make use-local p=<name> / make use-upstream p=<name> # swap a dep source
103
+ ```
104
+
105
+ **CI:** GitHub Actions runs on `push`, `pull_request`, and `v*.*.*` tags — it tests, lints, builds with `poetry build`, and on tags publishes via `twine upload -r internal-pypi`. Versioning is dynamic from the Git tag. Required CI config: `GCP_SERVICE_ACCOUNT_KEY` (secret) plus `GCP_REGION`, `GCP_PROJECT_NAME`, `GCP_PYPI` (variables).
106
+
107
+ ## License
108
+
109
+ AGPL-3.0-or-later — see [LICENSE](./LICENSE).
package/manifest.yaml ADDED
@@ -0,0 +1,464 @@
1
+ manifest_version: 1.0.0
2
+ name: spec-objects-security
3
+ version: 0.1.0
4
+ description: 'Filament Module: tier-2 security and identity ObjectTypes (threats, controls, auth flows, secrets, policies)'
5
+ nav:
6
+ category:
7
+ slug: security
8
+ label: Security
9
+ icon: ShieldCheck
10
+ order: 54
11
+ archetypes: []
12
+ grammars: []
13
+ artifact_types: []
14
+ lint_rules: []
15
+ object_types:
16
+ - name: auth_flow
17
+ roles: [externally-exposed]
18
+ allowed_links:
19
+ requires: [mfa_method]
20
+ grants: [role]
21
+ protects: [externally-exposed]
22
+ references: [auth_flow]
23
+ data_schema:
24
+ type: object
25
+ body_extraction:
26
+ yield_pattern:
27
+ match:
28
+ id: &id001
29
+ from: frontmatter_field
30
+ path:
31
+ - id
32
+ required: true
33
+ title: &id002
34
+ from: frontmatter_field
35
+ path:
36
+ - title
37
+ required: true
38
+ type: &id003
39
+ from: frontmatter_field
40
+ path:
41
+ - type
42
+ required: true
43
+ flow:
44
+ from: code_block
45
+ after_heading: Flow
46
+ required: true
47
+ language: mermaid
48
+ - name: permission
49
+ allowed_links:
50
+ grants: [scope]
51
+ guards: [externally-exposed]
52
+ references: [role]
53
+ data_schema:
54
+ type: object
55
+ body_extraction:
56
+ yield_pattern:
57
+ match:
58
+ id: *id001
59
+ title: *id002
60
+ type: *id003
61
+ resource:
62
+ from: frontmatter_field
63
+ path:
64
+ - resource
65
+ required: true
66
+ verb:
67
+ from: frontmatter_field
68
+ path:
69
+ - verb
70
+ required: true
71
+ - name: scope
72
+ allowed_links:
73
+ references: [permission]
74
+ data_schema:
75
+ type: object
76
+ body_extraction:
77
+ yield_pattern:
78
+ match:
79
+ id: *id001
80
+ title: *id002
81
+ type: *id003
82
+ grants:
83
+ from: section_body
84
+ after_heading: Grants
85
+ required: true
86
+ - name: role
87
+ allowed_links:
88
+ grants: [permission]
89
+ references: [role]
90
+ data_schema:
91
+ type: object
92
+ body_extraction:
93
+ yield_pattern:
94
+ match:
95
+ id: *id001
96
+ title: *id002
97
+ type: *id003
98
+ permissions:
99
+ from: section_body
100
+ after_heading: Permissions
101
+ required: true
102
+ - name: secret
103
+ roles: [sensitive]
104
+ allowed_links:
105
+ references: [secret]
106
+ data_schema:
107
+ type: object
108
+ body_extraction:
109
+ yield_pattern:
110
+ match:
111
+ id: *id001
112
+ title: *id002
113
+ type: *id003
114
+ rotation:
115
+ from: section_body
116
+ after_heading: Rotation
117
+ required: true
118
+ - name: encryption_key
119
+ roles: [sensitive]
120
+ allowed_links:
121
+ encrypts: [sensitive, data_classification]
122
+ data_schema:
123
+ type: object
124
+ body_extraction:
125
+ yield_pattern:
126
+ match:
127
+ id: *id001
128
+ title: *id002
129
+ type: *id003
130
+ algorithm:
131
+ from: frontmatter_field
132
+ path:
133
+ - algorithm
134
+ required: true
135
+ rotation:
136
+ from: frontmatter_field
137
+ path:
138
+ - rotation
139
+ required: false
140
+ - name: session_config
141
+ allowed_links:
142
+ references: [auth_flow, jwt_claim]
143
+ data_schema:
144
+ type: object
145
+ body_extraction:
146
+ yield_pattern:
147
+ match:
148
+ id: *id001
149
+ title: *id002
150
+ type: *id003
151
+ settings:
152
+ from: section_body
153
+ after_heading: Settings
154
+ required: true
155
+ - name: data_classification
156
+ allowed_links:
157
+ classifies: [persistable, externally-exposed, sensitive]
158
+ references: [data_classification]
159
+ data_schema:
160
+ type: object
161
+ body_extraction:
162
+ yield_pattern:
163
+ match:
164
+ id: *id001
165
+ title: *id002
166
+ type: *id003
167
+ handling:
168
+ from: section_body
169
+ after_heading: Handling
170
+ required: true
171
+ - name: trust_boundary
172
+ allowed_links:
173
+ contains: [asset, api_endpoint]
174
+ references: [trust_boundary]
175
+ data_schema:
176
+ type: object
177
+ body_extraction:
178
+ yield_pattern:
179
+ match:
180
+ id: *id001
181
+ title: *id002
182
+ type: *id003
183
+ boundary:
184
+ from: code_block
185
+ after_heading: Boundary
186
+ required: true
187
+ language: mermaid
188
+ - name: audit_event
189
+ roles: [event-like]
190
+ allowed_links:
191
+ references: [audit_event]
192
+ data_schema:
193
+ type: object
194
+ body_extraction:
195
+ yield_pattern:
196
+ match:
197
+ id: *id001
198
+ title: *id002
199
+ type: *id003
200
+ event_schema:
201
+ from: code_block
202
+ after_heading: Event Schema
203
+ required: true
204
+ language: json
205
+ - name: csrf_token
206
+ roles: [sensitive]
207
+ allowed_links:
208
+ references: [csrf_token]
209
+ data_schema:
210
+ type: object
211
+ body_extraction:
212
+ yield_pattern:
213
+ match:
214
+ id: *id001
215
+ title: *id002
216
+ type: *id003
217
+ rotation_window:
218
+ from: frontmatter_field
219
+ path:
220
+ - rotation_window
221
+ required: true
222
+ - name: cors_policy
223
+ allowed_links:
224
+ references: [cors_policy]
225
+ data_schema:
226
+ type: object
227
+ body_extraction:
228
+ yield_pattern:
229
+ match:
230
+ id: *id001
231
+ title: *id002
232
+ type: *id003
233
+ origins:
234
+ from: section_body
235
+ after_heading: Origins
236
+ required: true
237
+ - name: password_policy
238
+ allowed_links:
239
+ references: [password_policy]
240
+ data_schema:
241
+ type: object
242
+ body_extraction:
243
+ yield_pattern:
244
+ match:
245
+ id: *id001
246
+ title: *id002
247
+ type: *id003
248
+ rules:
249
+ from: section_body
250
+ after_heading: Rules
251
+ required: true
252
+ - name: mfa_method
253
+ allowed_links:
254
+ references: [mfa_method]
255
+ data_schema:
256
+ type: object
257
+ body_extraction:
258
+ yield_pattern:
259
+ match:
260
+ id: *id001
261
+ title: *id002
262
+ type: *id003
263
+ kind:
264
+ from: frontmatter_field
265
+ path:
266
+ - kind
267
+ required: true
268
+ factor:
269
+ from: frontmatter_field
270
+ path:
271
+ - factor
272
+ required: false
273
+ - name: jwt_claim
274
+ roles: [sensitive]
275
+ allowed_links:
276
+ references: [jwt_claim]
277
+ data_schema:
278
+ type: object
279
+ body_extraction:
280
+ yield_pattern:
281
+ match:
282
+ id: *id001
283
+ title: *id002
284
+ type: *id003
285
+ schema_json:
286
+ from: code_block
287
+ after_heading: Schema
288
+ required: true
289
+ language: json
290
+ - name: threat
291
+ allowed_links:
292
+ threatens: [asset, attack_surface]
293
+ exploits: [vulnerability]
294
+ data_schema:
295
+ type: object
296
+ body_extraction:
297
+ yield_pattern:
298
+ match:
299
+ id: &id004
300
+ from: frontmatter_field
301
+ path:
302
+ - id
303
+ required: true
304
+ title: &id005
305
+ from: frontmatter_field
306
+ path:
307
+ - title
308
+ required: true
309
+ type: &id006
310
+ from: frontmatter_field
311
+ path:
312
+ - type
313
+ required: true
314
+ stride_category:
315
+ from: frontmatter_field
316
+ path:
317
+ - stride_category
318
+ required: true
319
+ vector:
320
+ from: frontmatter_field
321
+ path:
322
+ - vector
323
+ required: true
324
+ - name: control
325
+ allowed_links:
326
+ mitigates: [threat, risk, vulnerability]
327
+ implements: [policy]
328
+ protects: [externally-exposed, asset]
329
+ data_schema:
330
+ type: object
331
+ body_extraction:
332
+ yield_pattern:
333
+ match:
334
+ id: *id004
335
+ title: *id005
336
+ type: *id006
337
+ mappings:
338
+ from: section_body
339
+ after_heading: Mappings
340
+ required: true
341
+ - name: risk
342
+ allowed_links:
343
+ arises_from: [threat, vulnerability]
344
+ references: [asset]
345
+ data_schema:
346
+ type: object
347
+ body_extraction:
348
+ yield_pattern:
349
+ match:
350
+ id: *id004
351
+ title: *id005
352
+ type: *id006
353
+ likelihood:
354
+ from: frontmatter_field
355
+ path:
356
+ - likelihood
357
+ required: true
358
+ impact:
359
+ from: frontmatter_field
360
+ path:
361
+ - impact
362
+ required: true
363
+ - name: vulnerability
364
+ allowed_links:
365
+ affects: [asset, attack_surface]
366
+ references: [vulnerability]
367
+ data_schema:
368
+ type: object
369
+ body_extraction:
370
+ yield_pattern:
371
+ match:
372
+ id: *id004
373
+ title: *id005
374
+ type: *id006
375
+ cve_id:
376
+ from: frontmatter_field
377
+ path:
378
+ - cve_id
379
+ required: false
380
+ severity:
381
+ from: frontmatter_field
382
+ path:
383
+ - severity
384
+ required: true
385
+ - name: asset
386
+ allowed_links:
387
+ references: [data_classification, trust_boundary]
388
+ data_schema:
389
+ type: object
390
+ body_extraction:
391
+ yield_pattern:
392
+ match:
393
+ id: *id004
394
+ title: *id005
395
+ type: *id006
396
+ description_body:
397
+ from: section_body
398
+ after_heading: Description
399
+ required: true
400
+ - name: attack_surface
401
+ roles: [externally-exposed]
402
+ allowed_links:
403
+ exposes: [asset, api_endpoint]
404
+ data_schema:
405
+ type: object
406
+ body_extraction:
407
+ yield_pattern:
408
+ match:
409
+ id: *id004
410
+ title: *id005
411
+ type: *id006
412
+ entry_points:
413
+ from: section_body
414
+ after_heading: Entry Points
415
+ required: true
416
+ - name: policy
417
+ allowed_links:
418
+ governs: [control, role, permission, data_classification]
419
+ data_schema:
420
+ type: object
421
+ body_extraction:
422
+ yield_pattern:
423
+ match:
424
+ id: *id004
425
+ title: *id005
426
+ type: *id006
427
+ policy_body:
428
+ from: section_body
429
+ after_heading: Policy
430
+ required: true
431
+ - name: audit_finding
432
+ allowed_links:
433
+ traces_to: [risk]
434
+ references: [control, vulnerability, asset]
435
+ data_schema:
436
+ type: object
437
+ body_extraction:
438
+ yield_pattern:
439
+ match:
440
+ id: *id004
441
+ title: *id005
442
+ type: *id006
443
+ recommendation:
444
+ from: section_body
445
+ after_heading: Recommendation
446
+ required: true
447
+
448
+ # FR-043: concrete-term lexicon — security / identity / auth objects.
449
+ lexicon:
450
+ token: {definition: a bearer credential representing identity or grant}
451
+ secret: {definition: a confidential value (key, password) kept out of code}
452
+ scope: {definition: a named permission boundary on a grant}
453
+ claim: {definition: an assertion carried in a token}
454
+ permission: {definition: an allowed action on a resource}
455
+ credential: {definition: data proving an identity}
456
+ certificate: {definition: a signed binding of identity to a public key}
457
+ signature: {definition: a cryptographic proof of integrity/origin}
458
+ principal: {definition: an authenticated identity (user/service)}
459
+ keypair: {definition: a public/private cryptographic key pair}
460
+ nonce: {definition: a single-use value preventing replay}
461
+ hash: {definition: a fixed-size digest of input}
462
+ cipher: {definition: an encryption/decryption algorithm}
463
+ audit: {definition: a recorded, tamper-evident event trail}
464
+ policy: {definition: a declarative rule governing access or behavior}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@agent-ix/spec-objects-security",
3
+ "version": "0.4.0",
4
+ "description": "Filament Module: tier-2 security and identity ObjectTypes (threat, control, risk, asset, policy, auth_flow, role, scope, jwt_claim, secret)",
5
+ "license": "MIT",
6
+ "author": "Agent IX <agents@agent-ix.dev>",
7
+ "keywords": [
8
+ "agent-ix",
9
+ "filament",
10
+ "filament-module",
11
+ "quoin",
12
+ "quire",
13
+ "spec"
14
+ ],
15
+ "files": [
16
+ "manifest.yaml",
17
+ "schemas/",
18
+ "skeletons/",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "scripts": {
23
+ "prepack": "node scripts/stage-npm.mjs"
24
+ },
25
+ "publishConfig": {
26
+ "registry": "https://registry.npmjs.org/",
27
+ "access": "public"
28
+ },
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/agent-ix/spec-objects-security.git"
32
+ }
33
+ }
@@ -0,0 +1,22 @@
1
+ ---
2
+ id: ASSET-001
3
+ title: "Session store"
4
+ type: asset
5
+ ---
6
+ <!-- asset authoring skeleton (spec-objects-security). Fill every part with
7
+ substantive content. Contract (manifest body_extraction asserts):
8
+ - Frontmatter MUST carry id, title, type (type: asset).
9
+ - A "Description" section MUST describe the asset and why it matters. -->
10
+ # [ASSET-001] Session store
11
+
12
+ Redis cluster holding all active Atlas web sessions and CSRF token state.
13
+
14
+ ## Description
15
+
16
+ The session store is the authority for who is currently authenticated:
17
+ compromise yields the ability to mint or hijack any active session across all
18
+ tenants, bypassing AUTHFLOW-001 entirely. It runs in the trusted private
19
+ subnet behind BOUND-001, accepts only mTLS connections from the auth service,
20
+ and persists nothing to disk (RDB/AOF disabled) so session identifiers never
21
+ reach backup media. Data it holds is classified Confidential (CLASS-001
22
+ handling applies to the embedded user identifiers).
@@ -0,0 +1,25 @@
1
+ ---
2
+ id: SURF-001
3
+ title: "Atlas authentication attack surface"
4
+ type: attack_surface
5
+ ---
6
+ <!-- attack_surface authoring skeleton (spec-objects-security). Fill every part
7
+ with substantive content. Contract (manifest body_extraction asserts):
8
+ - Frontmatter MUST carry id, title, type (type:
9
+ attack_surface).
10
+ - An "Entry Points" section MUST enumerate the externally reachable
11
+ entry points. -->
12
+ # [SURF-001] Atlas authentication attack surface
13
+
14
+ Internet-reachable surface of the Atlas authentication subsystem, reviewed
15
+ whenever a new route crosses BOUND-001 outward.
16
+
17
+ ## Entry Points
18
+
19
+ - `GET /login`, `GET /callback` — browser OIDC endpoints (AUTHFLOW-001)
20
+ - `POST /api/token` — public API token exchange for integrations (SCOPE-001)
21
+ - `POST /auth/local` — break-glass local login, gated by PASSPOL-001 and
22
+ MFA-001
23
+ - `POST /webhooks/idp` — IdP back-channel logout receiver, HMAC-verified
24
+ - Session cookie and `X-Atlas-CSRF` header parsing in the gateway middleware
25
+ (CSRF-001), reachable from any authenticated page
@@ -0,0 +1,34 @@
1
+ ---
2
+ id: AUDIT-001
3
+ title: "user.login.succeeded audit event"
4
+ type: audit_event
5
+ ---
6
+ <!-- audit_event authoring skeleton (spec-objects-security). Fill every part
7
+ with substantive content. Contract (manifest body_extraction asserts):
8
+ - Frontmatter MUST carry id, title, type (type:
9
+ audit_event).
10
+ - An "Event Schema" section MUST carry a ```json code block with the
11
+ event's payload schema. -->
12
+ # [AUDIT-001] user.login.succeeded audit event
13
+
14
+ Emitted by the auth service when AUTHFLOW-001 completes and a session is
15
+ established. Appended to the tenant's immutable audit stream and queryable by
16
+ ROLE-001 (Tenant Auditor) holders.
17
+
18
+ ## Event Schema
19
+
20
+ ```json
21
+ {
22
+ "type": "object",
23
+ "required": ["event", "occurred_at", "tenant_id", "user_id", "source_ip"],
24
+ "properties": {
25
+ "event": { "const": "user.login.succeeded" },
26
+ "occurred_at": { "type": "string", "format": "date-time" },
27
+ "tenant_id": { "type": "string", "format": "uuid" },
28
+ "user_id": { "type": "string", "format": "uuid" },
29
+ "source_ip": { "type": "string" },
30
+ "idp_issuer": { "type": "string", "format": "uri" },
31
+ "mfa_method": { "type": "string", "enum": ["totp", "webauthn", "none"] }
32
+ }
33
+ }
34
+ ```