@agent-ix/spec-objects-security 0.5.0 → 0.8.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.
Files changed (77) hide show
  1. package/README.md +2 -0
  2. package/manifest.yaml +270 -26
  3. package/package.json +8 -2
  4. package/schemas/Asset.json +78 -0
  5. package/schemas/AttackSurface.json +55 -0
  6. package/schemas/AuditEvent.json +57 -0
  7. package/schemas/AuditFinding.json +67 -0
  8. package/schemas/AuthFlow.json +69 -0
  9. package/schemas/ConfidentialityLevel.json +13 -0
  10. package/schemas/Control.json +83 -0
  11. package/schemas/ControlEffectiveness.json +12 -0
  12. package/schemas/ControlMapping.json +26 -0
  13. package/schemas/CorsPolicy.json +46 -0
  14. package/schemas/CsrfToken.json +46 -0
  15. package/schemas/DataClassification.json +61 -0
  16. package/schemas/DefaultedField.json +14 -0
  17. package/schemas/EffectivenessField.json +15 -0
  18. package/schemas/EncryptionKey.json +78 -0
  19. package/schemas/FindingStatus.json +14 -0
  20. package/schemas/FlowStep.json +24 -0
  21. package/schemas/IdentityField.json +15 -0
  22. package/schemas/Impact.json +14 -0
  23. package/schemas/ImpactField.json +15 -0
  24. package/schemas/JwtClaim.json +49 -0
  25. package/schemas/LevelField.json +15 -0
  26. package/schemas/Likelihood.json +14 -0
  27. package/schemas/LikelihoodField.json +15 -0
  28. package/schemas/MfaFactorKind.json +12 -0
  29. package/schemas/MfaMethod.json +69 -0
  30. package/schemas/OccurrenceField.json +14 -0
  31. package/schemas/OccurrenceTypeRef.json +15 -0
  32. package/schemas/PasswordPolicy.json +40 -0
  33. package/schemas/Permission.json +49 -0
  34. package/schemas/Policy.json +52 -0
  35. package/schemas/Risk.json +79 -0
  36. package/schemas/Role.json +55 -0
  37. package/schemas/Scope.json +49 -0
  38. package/schemas/Secret.json +78 -0
  39. package/schemas/SecretLifecycle.json +14 -0
  40. package/schemas/SessionConfig.json +43 -0
  41. package/schemas/Severity.json +14 -0
  42. package/schemas/SeverityField.json +15 -0
  43. package/schemas/StatusField.json +15 -0
  44. package/schemas/StrideCategory.json +15 -0
  45. package/schemas/StrideCategoryField.json +15 -0
  46. package/schemas/Threat.json +67 -0
  47. package/schemas/TrustBoundary.json +69 -0
  48. package/schemas/TrustLevel.json +12 -0
  49. package/schemas/TrustLevelField.json +15 -0
  50. package/schemas/Vulnerability.json +70 -0
  51. package/schemas/toolchain.json +97 -0
  52. package/skeletons/asset.md +22 -14
  53. package/skeletons/asset.sysml.md +27 -0
  54. package/skeletons/attack_surface.md +21 -18
  55. package/skeletons/audit_event.md +31 -18
  56. package/skeletons/audit_finding.md +22 -18
  57. package/skeletons/auth_flow.md +56 -21
  58. package/skeletons/control.md +45 -16
  59. package/skeletons/cors_policy.md +33 -17
  60. package/skeletons/csrf_token.md +17 -13
  61. package/skeletons/data_classification.md +23 -18
  62. package/skeletons/encryption_key.md +19 -12
  63. package/skeletons/jwt_claim.md +25 -14
  64. package/skeletons/mfa_method.md +17 -11
  65. package/skeletons/password_policy.md +35 -20
  66. package/skeletons/permission.md +18 -12
  67. package/skeletons/policy.md +33 -17
  68. package/skeletons/risk.md +20 -14
  69. package/skeletons/role.md +20 -13
  70. package/skeletons/scope.md +18 -12
  71. package/skeletons/secret.md +23 -16
  72. package/skeletons/secret.sysml.md +29 -0
  73. package/skeletons/session_config.md +35 -18
  74. package/skeletons/threat.md +22 -11
  75. package/skeletons/threat.sysml.md +26 -0
  76. package/skeletons/trust_boundary.md +51 -16
  77. package/skeletons/vulnerability.md +18 -15
@@ -1,26 +1,41 @@
1
1
  ---
2
- id: PASSPOL-001
3
- title: "Atlas local-account password policy"
2
+ id: PWD-001
3
+ title: "TenantPasswordPolicy"
4
4
  type: password_policy
5
+ object: password_policy
5
6
  ---
6
- <!-- password_policy 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
- password_policy).
10
- - A "Rules" section MUST enumerate the enforced password rules. -->
11
- # [PASSPOL-001] Atlas local-account password policy
12
-
13
- Applies to break-glass local accounts only; regular tenant users authenticate
14
- through their IdP (AUTHFLOW-001). Aligned with NIST SP 800-63B: length over
15
- composition, breach screening over forced rotation.
7
+ <!-- password_policy authoring skeleton (spec-objects-security). Fill every section with
8
+ substantive content. Contract (manifest body_extraction asserts):
9
+ - Frontmatter MUST carry id, title, type: password_policy,
10
+ object: password_policy.
11
+ - "## Rules" (H2, required): the rules in prose.
12
+ - "## Invariants" (H2): PasswordPolicy.json requires at least one clause. -->
13
+ # [PWD-001] TenantPasswordPolicy
16
14
 
17
15
  ## Rules
18
16
 
19
- - Minimum length 12 characters, maximum 128; all printable Unicode accepted
20
- - Candidate passwords are checked against the haveibeenpwned k-anonymity API
21
- and rejected when previously breached
22
- - No mandatory periodic rotation; rotation is forced only on suspected
23
- compromise
24
- - Hashing: argon2id with memory 64 MiB, iterations 3, parallelism 4
25
- - Rate limiting: 10 failed attempts per account per 15 minutes, then lockout
26
- with audit event emission
17
+ - At least 12 characters, with no composition rule beyond length.
18
+ - Checked against the breached-password corpus at set time and at each login.
19
+ - No forced periodic expiry; rotation is event-driven on suspected compromise.
20
+ - Stored only as an Argon2id digest, never reversibly.
21
+
22
+ ## Invariants
23
+
24
+ The clauses this policy asserts. Each clause owns one `ocl` fence under its
25
+ own `### <clauseId>` heading.
26
+
27
+ ### MinimumLengthIsTwelve
28
+
29
+ ```ocl
30
+ context TenantPasswordPolicy
31
+ inv MinimumLengthIsTwelve:
32
+ self.minimum_length >= 12
33
+ ```
34
+
35
+ ### NoReversibleStorage
36
+
37
+ ```ocl
38
+ context TenantPasswordPolicy
39
+ inv NoReversibleStorage:
40
+ self.storage_scheme = 'argon2id'
41
+ ```
@@ -1,18 +1,24 @@
1
1
  ---
2
2
  id: PERM-001
3
- title: "Read tenant documents"
3
+ title: "ExportTenantData"
4
4
  type: permission
5
- resource: document
6
- verb: read
5
+ object: permission
6
+ resource: "tenant/records"
7
+ verb: export
7
8
  ---
8
- <!-- permission authoring skeleton (spec-objects-security). Fill every part with
9
+ <!-- permission authoring skeleton (spec-objects-security). Fill every section with
9
10
  substantive content. Contract (manifest body_extraction asserts):
10
- - Frontmatter MUST carry id, title, type (type: permission).
11
- - Frontmatter MUST carry resource and verb (the protected resource kind and
12
- the action this permission grants on it). -->
13
- # [PERM-001] Read tenant documents
11
+ - Frontmatter MUST carry id, title, type: permission, object: permission,
12
+ resource (what is acted on) and verb (the action).
13
+ - "## Properties" (H2): the typed declaration. Permission.json forbids
14
+ `operations` and `relations`. -->
15
+ # [PERM-001] ExportTenantData
14
16
 
15
- Grants read access to `document` resources within the caller's tenant. Checked
16
- by the Atlas policy engine on every `GET /api/documents/*` request; the
17
- permission is satisfied only when the resource's `tenant_id` matches the
18
- caller's `tenant_id` claim.
17
+ ## Properties
18
+
19
+ | Field | Type | Multiplicity | Constraints |
20
+ |---|---|---|---|
21
+ | permission_id | UUID | 1..1 | identity |
22
+ | resource | String | 1..1 | minLength: 1 |
23
+ | verb | String | 1..1 | minLength: 1 |
24
+ | requires_reauthentication | Boolean | 1..1 | |
@@ -1,25 +1,41 @@
1
1
  ---
2
2
  id: POL-001
3
- title: "Access token lifetime policy"
3
+ title: "LeastPrivilegeAccessPolicy"
4
4
  type: policy
5
+ object: policy
5
6
  ---
6
- <!-- policy authoring skeleton (spec-objects-security). Fill every part with
7
+ <!-- policy authoring skeleton (spec-objects-security). Fill every section with
7
8
  substantive content. Contract (manifest body_extraction asserts):
8
- - Frontmatter MUST carry id, title, type (type: policy).
9
- - A "Policy" section MUST state the normative policy text. -->
10
- # [POL-001] Access token lifetime policy
11
-
12
- Platform-wide policy governing bearer token lifetimes for Atlas first-party
13
- and integration clients, owned by the security council.
9
+ - Frontmatter MUST carry id, title, type: policy, object: policy.
10
+ - "## Policy" (H2, required): the rule in prose.
11
+ - "## Invariants" (H2): Policy.json requires at least one clause — a
12
+ policy that constrains nothing is prose. -->
13
+ # [POL-001] LeastPrivilegeAccessPolicy
14
14
 
15
15
  ## Policy
16
16
 
17
- - Access tokens SHALL expire no later than 15 minutes after issuance.
18
- - Refresh tokens SHALL be single-use and rotated on every exchange (CTRL-001),
19
- with an absolute family lifetime of 30 days.
20
- - Integration tokens issued for SCOPE-001 SHALL be bound to the requesting
21
- client via DPoP or mTLS sender constraint.
22
- - Token lifetimes SHALL NOT be extended per tenant without a documented risk
23
- acceptance referencing the affected risk register entries (e.g. RISK-001).
24
- - Violations surface as audit findings (FIND-001 family) and block release
25
- until remediated or formally accepted.
17
+ Every principal holds the smallest set of permissions that lets it complete
18
+ its task, for the shortest time that task takes. A grant that is not exercised
19
+ within 90 days is revoked automatically, and every standing administrative
20
+ grant is reviewed quarterly by the tenant owner.
21
+
22
+ ## Invariants
23
+
24
+ The clauses this policy asserts. Each clause owns one `ocl` fence under its
25
+ own `### <clauseId>` heading.
26
+
27
+ ### NoStandingAdministrativeGrant
28
+
29
+ ```ocl
30
+ context LeastPrivilegeAccessPolicy
31
+ inv NoStandingAdministrativeGrant:
32
+ TenantAdministrator.allInstances()->forAll(r | r.assignable_by_self_service = false)
33
+ ```
34
+
35
+ ### UnusedGrantsExpire
36
+
37
+ ```ocl
38
+ context LeastPrivilegeAccessPolicy
39
+ inv UnusedGrantsExpire:
40
+ TenantReadScope.allInstances()->forAll(s | s.includes_write = false)
41
+ ```
package/skeletons/risk.md CHANGED
@@ -1,20 +1,26 @@
1
1
  ---
2
2
  id: RISK-001
3
- title: "Tenant account takeover via token theft"
3
+ title: "TenantDataExfiltration"
4
4
  type: risk
5
- likelihood: medium
6
- impact: high
5
+ object: risk
6
+ likelihood: possible
7
+ impact: major
7
8
  ---
8
- <!-- risk authoring skeleton (spec-objects-security). Fill every part with
9
+ <!-- risk authoring skeleton (spec-objects-security). Fill every section with
9
10
  substantive content. Contract (manifest body_extraction asserts):
10
- - Frontmatter MUST carry id, title, type (type: risk),
11
- likelihood and impact (qualitative ratings driving the risk score). -->
12
- # [RISK-001] Tenant account takeover via token theft
11
+ - Frontmatter MUST carry id, title, type: risk, object: risk,
12
+ likelihood and impact.
13
+ - "## Properties" (H2): the typed declaration. Risk.json requires both a
14
+ `likelihood` row and an `impact` row: a risk scored on one axis is
15
+ not scored. -->
16
+ # [RISK-001] TenantDataExfiltration
13
17
 
14
- Business risk that an attacker realizes THREAT-001 and operates inside a
15
- tenant with the victim's permissions, exposing Confidential PII (CLASS-001)
16
- and enabling data exfiltration through the public API. Likelihood is rated
17
- medium given commodity infostealer malware; impact is high because a single
18
- takeover crosses into regulated personal data. Primary treatment is CTRL-001;
19
- residual risk after rotation and reuse detection is accepted at low/high and
20
- reviewed quarterly by the security council.
18
+ ## Properties
19
+
20
+ | Field | Type | Multiplicity | Constraints |
21
+ |---|---|---|---|
22
+ | risk_id | UUID | 1..1 | identity |
23
+ | likelihood | String | 1..1 | minLength: 1 |
24
+ | impact | String | 1..1 | minLength: 1 |
25
+ | assessed_at | Timestamp | 1..1 | |
26
+ | affected_asset | TenantDatabase | 0..1 | |
package/skeletons/role.md CHANGED
@@ -1,21 +1,28 @@
1
1
  ---
2
2
  id: ROLE-001
3
- title: "Tenant Auditor role"
3
+ title: "TenantAdministrator"
4
4
  type: role
5
+ object: role
5
6
  ---
6
- <!-- role authoring skeleton (spec-objects-security). Fill every part with
7
+ <!-- role authoring skeleton (spec-objects-security). Fill every section with
7
8
  substantive content. Contract (manifest body_extraction asserts):
8
- - Frontmatter MUST carry id, title, type (type: role).
9
- - A "Permissions" section MUST list the permissions bound to the role. -->
10
- # [ROLE-001] Tenant Auditor role
11
-
12
- Read-only compliance role assigned to external auditors invited into a tenant.
13
- Members can inspect configuration and audit trails but can never mutate state
14
- or read secret material.
9
+ - Frontmatter MUST carry id, title, type: role, object: role.
10
+ - "## Permissions" (H2, required): the permissions this role grants.
11
+ - "## Properties" (H2): the typed declaration. A role is a grant set,
12
+ so Role.json forbids `operations`. -->
13
+ # [ROLE-001] TenantAdministrator
15
14
 
16
15
  ## Permissions
17
16
 
18
- - PERM-001 — `document:read` within the tenant
19
- - `audit_log:read` — query the tenant's audit event stream
20
- - `policy:read` — view security policies in effect for the tenant
21
- - `member:list` — enumerate tenant members and their role bindings
17
+ - ExportTenantData — export the tenant's records for portability requests.
18
+ - Manage tenant members, their roles and their MFA enrolment.
19
+ - Read the tenant audit trail; the role grants no write on it.
20
+
21
+ ## Properties
22
+
23
+ | Field | Type | Multiplicity | Constraints |
24
+ |---|---|---|---|
25
+ | role_id | UUID | 1..1 | identity |
26
+ | name | String | 1..1 | minLength: 1, maxLength: 64 |
27
+ | granted_permission | ExportTenantData | 0..1 | |
28
+ | assignable_by_self_service | Boolean | 1..1 | |
@@ -1,20 +1,26 @@
1
1
  ---
2
2
  id: SCOPE-001
3
- title: "documents:read OAuth scope"
3
+ title: "TenantReadScope"
4
4
  type: scope
5
+ object: scope
5
6
  ---
6
- <!-- scope authoring skeleton (spec-objects-security). Fill every part with
7
+ <!-- scope authoring skeleton (spec-objects-security). Fill every section with
7
8
  substantive content. Contract (manifest body_extraction asserts):
8
- - Frontmatter MUST carry id, title, type (type: scope).
9
- - A "Grants" section MUST list the permissions this scope confers. -->
10
- # [SCOPE-001] documents:read OAuth scope
11
-
12
- OAuth 2.0 scope requested by third-party integrations that need read-only
13
- access to a tenant's document library through the Atlas public API.
9
+ - Frontmatter MUST carry id, title, type: scope, object: scope.
10
+ - "## Grants" (H2, required): what a bearer of this scope may do.
11
+ - "## Properties" (H2): the typed declaration. -->
12
+ # [SCOPE-001] TenantReadScope
14
13
 
15
14
  ## Grants
16
15
 
17
- - PERM-001 — read tenant documents (`document:read`)
18
- - Listing document metadata via `GET /api/documents`
19
- - Downloading rendered document content via `GET /api/documents/{id}/content`
20
- - Excludes all write verbs; mutations require the separate `documents:write` scope
16
+ - Read access to the tenant's own records and audit trail.
17
+ - No write, no export, and no access to any other tenant.
18
+ - Downgrades to nothing when the grant family is revoked.
19
+
20
+ ## Properties
21
+
22
+ | Field | Type | Multiplicity | Constraints |
23
+ |---|---|---|---|
24
+ | scope_id | UUID | 1..1 | identity |
25
+ | name | String | 1..1 | minLength: 1, maxLength: 64 |
26
+ | includes_write | Boolean | 1..1 | |
@@ -1,25 +1,32 @@
1
1
  ---
2
2
  id: SECRET-001
3
- title: "Atlas OIDC client secret"
3
+ title: "AtlasOidcClientSecret"
4
4
  type: secret
5
+ object: secret
5
6
  ---
6
- <!-- secret authoring skeleton (spec-objects-security). Fill every part with
7
+ <!-- secret authoring skeleton (spec-objects-security). Fill every section with
7
8
  substantive content. Contract (manifest body_extraction asserts):
8
- - Frontmatter MUST carry id, title, type (type: secret).
9
- - A "Rotation" section MUST describe how and how often the secret rotates. -->
10
- # [SECRET-001] Atlas OIDC client secret
11
-
12
- Confidential client secret used by the Atlas backend to authenticate at the
13
- tenant IdP token endpoint during the authorization code exchange. Stored in
14
- Vault at `secret/atlas/oidc/client-secret`; never present in environment files
15
- or images.
9
+ - Frontmatter MUST carry id, title, type: secret, object: secret.
10
+ - "## Rotation" (H2, required): how and how often the secret rotates.
11
+ - "## Properties" (H2): the typed declaration. Secret.json admits no
12
+ field carrying a default, so the material can never be embedded here —
13
+ a secret is named by its locator only. -->
14
+ # [SECRET-001] AtlasOidcClientSecret
16
15
 
17
16
  ## Rotation
18
17
 
19
- - Rotated every 90 days by the `secret-rotator` job, which registers a new
18
+ - Rotated every 90 days by the `secret-rotator` job, which registers the new
20
19
  secret with the IdP before revoking the old one.
21
- - Dual-secret overlap window of 24 hours so in-flight token exchanges complete
22
- against either value.
23
- - Emergency rotation is triggered manually via `atlasctl secrets rotate
24
- oidc-client` and completes in under 5 minutes.
25
- - Every rotation emits an `secret.rotated` audit event (AUDIT-001 family).
20
+ - Dual-secret overlap window of 24 hours so in-flight exchanges complete.
21
+ - Emergency rotation completes in under 5 minutes.
22
+ - Every rotation emits a `secret.rotated` audit event.
23
+
24
+ ## Properties
25
+
26
+ | Field | Type | Multiplicity | Constraints |
27
+ |---|---|---|---|
28
+ | secret_id | UUID | 1..1 | identity |
29
+ | locator | String | 1..1 | minLength: 1 |
30
+ | lifecycle | String | 1..1 | minLength: 1 |
31
+ | rotation_period | Duration | 1..1 | |
32
+ | overlap_window | Duration | 0..1 | |
@@ -0,0 +1,29 @@
1
+ ---
2
+ id: SECRET-001
3
+ title: "AtlasOidcClientSecret"
4
+ type: secret
5
+ object: secret
6
+ ---
7
+ <!-- secret authoring skeleton, alternate Properties form. Declares exactly the
8
+ same fields as secret.md, authored as one ```sysml``` fence instead of the
9
+ typed table (FR-005-AC-2). No row carries a value: a secret is named by
10
+ its locator only (FR-006). -->
11
+ # [SECRET-001] AtlasOidcClientSecret
12
+
13
+ ## Rotation
14
+
15
+ - Rotated every 90 days by the `secret-rotator` job, which registers the new
16
+ secret with the IdP before revoking the old one.
17
+ - Dual-secret overlap window of 24 hours so in-flight exchanges complete.
18
+ - Emergency rotation completes in under 5 minutes.
19
+ - Every rotation emits a `secret.rotated` audit event.
20
+
21
+ ## Properties
22
+
23
+ ```sysml
24
+ attribute secret_id : UUID[1..1] { identity }
25
+ attribute locator : String[1..1] { minLength: 1 }
26
+ attribute lifecycle : String[1..1] { minLength: 1 }
27
+ attribute rotation_period : Duration[1..1]
28
+ attribute overlap_window : Duration[0..1]
29
+ ```
@@ -1,24 +1,41 @@
1
1
  ---
2
- id: SESSION-001
3
- title: "Atlas web session configuration"
2
+ id: SESS-001
3
+ title: "TenantSessionConfig"
4
4
  type: session_config
5
+ object: session_config
5
6
  ---
6
- <!-- session_config 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
- session_config).
10
- - A "Settings" section MUST enumerate the effective session parameters. -->
11
- # [SESSION-001] Atlas web session configuration
12
-
13
- Server-side session established after AUTHFLOW-001 completes. Session state
14
- lives in the session store (ASSET-001); the browser holds only an opaque
15
- identifier.
7
+ <!-- session_config authoring skeleton (spec-objects-security). Fill every section with
8
+ substantive content. Contract (manifest body_extraction asserts):
9
+ - Frontmatter MUST carry id, title, type: session_config,
10
+ object: session_config.
11
+ - "## Settings" (H2, required): the settings in prose.
12
+ - "## Invariants" (H2): SessionConfig.json requires at least one clause. -->
13
+ # [SESS-001] TenantSessionConfig
16
14
 
17
15
  ## Settings
18
16
 
19
- - Cookie name: `__Host-atlas_session` (Secure, HttpOnly, SameSite=Lax, Path=/)
20
- - Idle timeout: 30 minutes, sliding on authenticated requests
21
- - Absolute lifetime: 12 hours, after which re-authentication is required
22
- - Session identifier: 256-bit random value, regenerated on login and on
23
- privilege elevation
24
- - Concurrent session cap: 5 per user; the oldest session is evicted on overflow
17
+ - Idle timeout 30 minutes; absolute lifetime 12 hours.
18
+ - Cookie flags `HttpOnly`, `Secure`, `SameSite=Lax`, host-only.
19
+ - Session identifier rotates on every privilege change.
20
+ - Bound to AuthorizationCodeWithPkce; no session outlives its grant family.
21
+
22
+ ## Invariants
23
+
24
+ The clauses this configuration asserts. Each clause owns one `ocl` fence under
25
+ its own `### <clauseId>` heading.
26
+
27
+ ### IdleTimeoutIsShorterThanAbsoluteLifetime
28
+
29
+ ```ocl
30
+ context TenantSessionConfig
31
+ inv IdleTimeoutIsShorterThanAbsoluteLifetime:
32
+ self.idle_timeout < self.absolute_lifetime
33
+ ```
34
+
35
+ ### IdentifierRotatesOnPrivilegeChange
36
+
37
+ ```ocl
38
+ context TenantSessionConfig
39
+ inv IdentifierRotatesOnPrivilegeChange:
40
+ self.rotate_on_privilege_change
41
+ ```
@@ -1,19 +1,30 @@
1
1
  ---
2
2
  id: THREAT-001
3
- title: "Stolen refresh token replay"
3
+ title: "RefreshTokenReplay"
4
4
  type: threat
5
+ object: threat
5
6
  stride_category: Spoofing
6
7
  vector: "exfiltrated refresh token replayed from attacker infrastructure"
7
8
  ---
8
- <!-- threat authoring skeleton (spec-objects-security). Fill every part with
9
+ <!-- threat authoring skeleton (spec-objects-security). Fill every section with
9
10
  substantive content. Contract (manifest body_extraction asserts):
10
- - Frontmatter MUST carry id, title, type (type: threat),
11
- stride_category (STRIDE class) and vector (the attack vector). -->
12
- # [THREAT-001] Stolen refresh token replay
11
+ - Frontmatter MUST carry id, title, type: threat, object: threat,
12
+ stride_category (STRIDE class) and vector (the attack vector).
13
+ - "## Properties" (H2): the typed declaration. Threat.json requires an
14
+ identity row AND a `stride_category` row: a threat nobody classified
15
+ is not a threat statement. -->
16
+ # [THREAT-001] RefreshTokenReplay
13
17
 
14
- An attacker who exfiltrates a refresh token (malware on the user device, log
15
- leakage, or proxy compromise) replays it against the token endpoint to mint
16
- fresh access tokens and impersonate the user across tenant APIs. Detection is
17
- hard because requests are well-formed; mitigation relies on refresh token
18
- rotation with reuse detection (CTRL-001) and the trust boundary controls in
19
- BOUND-001. Realized risk is tracked as RISK-001.
18
+ An attacker who exfiltrates a refresh token replays it against the token
19
+ endpoint to mint fresh access tokens and impersonate the user across tenant
20
+ APIs. Detection is hard because the requests are well formed; mitigation
21
+ relies on RefreshTokenRotation and the TenantNetworkEdge controls.
22
+
23
+ ## Properties
24
+
25
+ | Field | Type | Multiplicity | Constraints |
26
+ |---|---|---|---|
27
+ | threat_id | UUID | 1..1 | identity |
28
+ | stride_category | String | 1..1 | minLength: 1 |
29
+ | vector | String | 1..1 | minLength: 1 |
30
+ | first_observed_at | Timestamp | 0..1 | |
@@ -0,0 +1,26 @@
1
+ ---
2
+ id: THREAT-001
3
+ title: "RefreshTokenReplay"
4
+ type: threat
5
+ object: threat
6
+ stride_category: Spoofing
7
+ vector: "exfiltrated refresh token replayed from attacker infrastructure"
8
+ ---
9
+ <!-- threat authoring skeleton, alternate Properties form. Declares exactly the
10
+ same fields as threat.md, authored as one ```sysml``` fence instead of the
11
+ typed table (FR-005-AC-2). -->
12
+ # [THREAT-001] RefreshTokenReplay
13
+
14
+ An attacker who exfiltrates a refresh token replays it against the token
15
+ endpoint to mint fresh access tokens and impersonate the user across tenant
16
+ APIs. Detection is hard because the requests are well formed; mitigation
17
+ relies on RefreshTokenRotation and the TenantNetworkEdge controls.
18
+
19
+ ## Properties
20
+
21
+ ```sysml
22
+ attribute threat_id : UUID[1..1] { identity }
23
+ attribute stride_category : String[1..1] { minLength: 1 }
24
+ attribute vector : String[1..1] { minLength: 1 }
25
+ attribute first_observed_at : Timestamp[0..1]
26
+ ```
@@ -1,26 +1,61 @@
1
1
  ---
2
2
  id: BOUND-001
3
- title: "Edge to auth-service trust boundary"
3
+ title: "TenantNetworkEdge"
4
4
  type: trust_boundary
5
+ object: trust_boundary
5
6
  ---
6
- <!-- trust_boundary 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
- trust_boundary).
10
- - A "Boundary" section MUST carry a ```mermaid code block drawing the
11
- boundary between zones. -->
12
- # [BOUND-001] Edge to auth-service trust boundary
13
-
14
- Separates the untrusted internet, the DMZ-hosted API gateway, and the trusted
15
- private subnet where the Atlas auth service and session store run. Everything
16
- crossing inward is re-authenticated: the gateway terminates TLS and the auth
17
- service only accepts mTLS connections from the gateway.
7
+ <!-- trust_boundary authoring skeleton (spec-objects-security). Fill every section with
8
+ substantive content. Contract (manifest body_extraction asserts):
9
+ - Frontmatter MUST carry id, title, type: trust_boundary,
10
+ object: trust_boundary.
11
+ - "## Boundary" (H2, required) holds a fenced `mermaid` diagram.
12
+ - "## Properties" (H2): requires an identity row and a `trust_level` row.
13
+ - "## Invariants" (H2): TrustBoundary.json requires at least one clause —
14
+ a boundary that admits every crossing is not a boundary. -->
15
+ # [BOUND-001] TenantNetworkEdge
18
16
 
19
17
  ## Boundary
20
18
 
21
19
  ```mermaid
22
20
  flowchart LR
23
- Browser["Tenant browser (untrusted)"] -->|HTTPS| Gateway["API gateway (DMZ)"]
24
- Gateway -->|mTLS| AuthSvc["Auth service (trusted subnet)"]
25
- AuthSvc -->|private VPC peering| SessionDB[("Session store")]
21
+ subgraph untrusted[Internet]
22
+ client[Tenant browser]
23
+ end
24
+ subgraph trusted[Tenant VPC]
25
+ api[Token endpoint]
26
+ db[(Tenant database)]
27
+ end
28
+ client -->|TLS 1.3, authenticated| api
29
+ api --> db
30
+ ```
31
+
32
+ ## Properties
33
+
34
+ | Field | Type | Multiplicity | Constraints |
35
+ |---|---|---|---|
36
+ | boundary_id | UUID | 1..1 | identity |
37
+ | trust_level | String | 1..1 | minLength: 1 |
38
+ | ingress_protocol | String | 1..1 | minLength: 1 |
39
+ | mutual_tls_required | Boolean | 1..1 | |
40
+
41
+ ## Invariants
42
+
43
+ The clauses this boundary enforces on every crossing. Each clause owns one
44
+ `ocl` fence under its own `### <clauseId>` heading; the fence text is carried
45
+ verbatim and never evaluated here.
46
+
47
+ ### EveryCrossingIsAuthenticated
48
+
49
+ ```ocl
50
+ context TenantNetworkEdge
51
+ inv EveryCrossingIsAuthenticated:
52
+ self.ingress_protocol <> 'plaintext'
53
+ ```
54
+
55
+ ### UntrustedSideNeverReachesTheDatabase
56
+
57
+ ```ocl
58
+ context TenantNetworkEdge
59
+ inv UntrustedSideNeverReachesTheDatabase:
60
+ self.trust_level = 'untrusted' implies self.mutual_tls_required
26
61
  ```
@@ -1,21 +1,24 @@
1
1
  ---
2
2
  id: VULN-001
3
- title: "Session store affected by lua sandbox escape"
3
+ title: "UnrotatedRefreshToken"
4
4
  type: vulnerability
5
- cve_id: CVE-2024-31449
5
+ object: vulnerability
6
6
  severity: high
7
+ cve_id: "CVE-2026-00000"
7
8
  ---
8
- <!-- vulnerability authoring skeleton (spec-objects-security). Fill every part
9
- with substantive content. Contract (manifest body_extraction asserts):
10
- - Frontmatter MUST carry id, title, type (type:
11
- vulnerability) and severity; cve_id links the public advisory when one
12
- exists. -->
13
- # [VULN-001] Session store affected by lua sandbox escape
9
+ <!-- vulnerability authoring skeleton (spec-objects-security). Fill every section with
10
+ substantive content. Contract (manifest body_extraction asserts):
11
+ - Frontmatter MUST carry id, title, type: vulnerability,
12
+ object: vulnerability and severity; cve_id is optional.
13
+ - "## Properties" (H2): the typed declaration. Vulnerability.json
14
+ requires a `severity` row, so an unscored weakness is refused. -->
15
+ # [VULN-001] UnrotatedRefreshToken
14
16
 
15
- The Redis instance backing the Atlas session store (ASSET-001) runs a version
16
- affected by CVE-2024-31449, where an authenticated client can craft a Lua
17
- script that escapes the sandbox and executes arbitrary code. Exploitation
18
- requires script-eval privileges inside the private subnet, so exposure is
19
- limited by BOUND-001, but a compromised service credential would yield full
20
- session-store control. Remediation: upgrade to a patched Redis release and
21
- disable `EVAL` for application credentials; tracked to closure in FIND-001.
17
+ ## Properties
18
+
19
+ | Field | Type | Multiplicity | Constraints |
20
+ |---|---|---|---|
21
+ | vulnerability_id | UUID | 1..1 | identity |
22
+ | severity | String | 1..1 | minLength: 1 |
23
+ | affected_component | String | 1..1 | minLength: 1 |
24
+ | disclosed_at | Timestamp | 0..1 | |