@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.
@@ -0,0 +1,25 @@
1
+ ---
2
+ id: FIND-001
3
+ title: "Session store reachable with eval privileges"
4
+ type: audit_finding
5
+ ---
6
+ <!-- audit_finding 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_finding).
10
+ - A "Recommendation" section MUST state the remediation recommendation. -->
11
+ # [FIND-001] Session store reachable with eval privileges
12
+
13
+ Q2 security audit finding: the auth service connects to the session store
14
+ (ASSET-001) with a credential that retains `EVAL`/`SCRIPT` privileges,
15
+ unnecessarily exposing the Lua sandbox surface of VULN-001 to any service
16
+ compromise. Severity high; affected component is the shared Redis ACL profile.
17
+
18
+ ## Recommendation
19
+
20
+ Upgrade the session store to a release patched for CVE-2024-31449, then
21
+ replace the shared ACL profile with a least-privilege profile that removes
22
+ `EVAL`, `EVALSHA` and `SCRIPT` for application credentials. Verify with an
23
+ integration test that script commands are rejected, and re-scan the cluster.
24
+ Target closure within 30 days; interim compensating control is the mTLS
25
+ restriction at BOUND-001.
@@ -0,0 +1,31 @@
1
+ ---
2
+ id: AUTHFLOW-001
3
+ title: "Tenant user login via OIDC authorization code flow"
4
+ type: auth_flow
5
+ ---
6
+ <!-- auth_flow 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: auth_flow).
9
+ - A "Flow" section MUST carry a ```mermaid code block describing the flow. -->
10
+ # [AUTHFLOW-001] Tenant user login via OIDC authorization code flow
11
+
12
+ Interactive browser login for Atlas tenant users. The platform delegates
13
+ authentication to the tenant's OIDC identity provider using the authorization
14
+ code flow with PKCE, then establishes a first-party session cookie.
15
+
16
+ ## Flow
17
+
18
+ ```mermaid
19
+ sequenceDiagram
20
+ participant Browser
21
+ participant AtlasApp
22
+ participant IdP
23
+ Browser->>AtlasApp: GET /login
24
+ AtlasApp->>Browser: 302 to IdP authorize endpoint with PKCE challenge
25
+ Browser->>IdP: GET /authorize with client_id and code_challenge
26
+ IdP->>Browser: 302 back with authorization code
27
+ Browser->>AtlasApp: GET /callback with code and state
28
+ AtlasApp->>IdP: POST /token with code and code_verifier
29
+ IdP->>AtlasApp: ID token plus access and refresh tokens
30
+ AtlasApp->>Browser: Set __Host-atlas_session cookie and redirect
31
+ ```
@@ -0,0 +1,24 @@
1
+ ---
2
+ id: CTRL-001
3
+ title: "Refresh token rotation with reuse detection"
4
+ type: control
5
+ ---
6
+ <!-- control 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: control).
9
+ - A "Mappings" section MUST map the control to threats, risks and
10
+ standards it addresses. -->
11
+ # [CTRL-001] Refresh token rotation with reuse detection
12
+
13
+ Every refresh-token exchange invalidates the presented token and issues a new
14
+ one bound to the same grant family. Presenting an already-rotated token marks
15
+ the whole family compromised: all descendants are revoked, the session is
16
+ terminated, and a `token.reuse.detected` audit event fires.
17
+
18
+ ## Mappings
19
+
20
+ - Mitigates THREAT-001 (stolen refresh token replay)
21
+ - Reduces RISK-001 likelihood from medium to low
22
+ - Implements OAuth 2.0 Security BCP (RFC 9700) refresh-token rotation guidance
23
+ - Maps to OWASP ASVS v4 §3.5.2 and NIST SP 800-63B §7.1 session binding
24
+ - Verified by integration test IT-001 (manifest activation roundtrip suite)
@@ -0,0 +1,24 @@
1
+ ---
2
+ id: CORS-001
3
+ title: "Atlas public API CORS policy"
4
+ type: cors_policy
5
+ ---
6
+ <!-- cors_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
+ cors_policy).
10
+ - An "Origins" section MUST enumerate the allowed origins and rules. -->
11
+ # [CORS-001] Atlas public API CORS policy
12
+
13
+ Browser cross-origin policy enforced by the API gateway for `api.atlas.example`.
14
+ Credentials are only honored for first-party app origins; integration origins
15
+ get token-based access without cookies.
16
+
17
+ ## Origins
18
+
19
+ - `https://app.atlas.example` — first-party web client; credentials allowed
20
+ - `https://admin.atlas.example` — operator console; credentials allowed
21
+ - `https://*.integrations.atlas.example` — sandboxed integration iframes;
22
+ credentials disallowed, `Authorization` header only
23
+ - All other origins are denied; no wildcard `*` is ever emitted, and preflight
24
+ responses are capped at `Access-Control-Max-Age: 600`
@@ -0,0 +1,19 @@
1
+ ---
2
+ id: CSRF-001
3
+ title: "Atlas web CSRF token"
4
+ type: csrf_token
5
+ rotation_window: 30m
6
+ ---
7
+ <!-- csrf_token authoring skeleton (spec-objects-security). Fill every part with
8
+ substantive content. Contract (manifest body_extraction asserts):
9
+ - Frontmatter MUST carry id, title, type (type:
10
+ csrf_token) and rotation_window (how often a fresh token is issued). -->
11
+ # [CSRF-001] Atlas web CSRF token
12
+
13
+ Double-submit CSRF token protecting all state-changing requests from the Atlas
14
+ web client. The server issues an HMAC-signed token bound to the session id
15
+ (SESSION-001); the client echoes it in the `X-Atlas-CSRF` header and the
16
+ middleware rejects any mutation where header and cookie values disagree. A
17
+ fresh token is minted every `30m` rotation window and on every login or
18
+ privilege elevation, with the previous token honored for one overlapping
19
+ window to avoid breaking in-flight forms.
@@ -0,0 +1,25 @@
1
+ ---
2
+ id: CLASS-001
3
+ title: "Confidential — tenant PII"
4
+ type: data_classification
5
+ ---
6
+ <!-- data_classification authoring skeleton (spec-objects-security). Fill every
7
+ part with substantive content. Contract (manifest body_extraction asserts):
8
+ - Frontmatter MUST carry id, title, type (type:
9
+ data_classification).
10
+ - A "Handling" section MUST state the handling requirements for the class. -->
11
+ # [CLASS-001] Confidential — tenant PII
12
+
13
+ Classification applied to tenant member profile data (names, email addresses,
14
+ phone numbers) and any document flagged as containing personal data. Sits one
15
+ level below `Restricted` (secrets and key material) in the Atlas scheme.
16
+
17
+ ## Handling
18
+
19
+ - Encrypted at rest with the tenant DEK (KEY-001) and in transit via TLS 1.3.
20
+ - Access requires an explicit permission grant; reads are recorded in the
21
+ audit stream as `pii.accessed` events.
22
+ - Never written to application logs; log scrubbing rejects payloads matching
23
+ the PII field allowlist.
24
+ - Exported only through the GDPR export pipeline, with tenant-admin approval.
25
+ - Retention: deleted within 30 days of tenant offboarding or subject request.
@@ -0,0 +1,18 @@
1
+ ---
2
+ id: KEY-001
3
+ title: "Tenant data encryption key"
4
+ type: encryption_key
5
+ algorithm: AES-256-GCM
6
+ rotation: 90d
7
+ ---
8
+ <!-- encryption_key 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
+ encryption_key) and algorithm (the cipher); rotation gives the cadence. -->
12
+ # [KEY-001] Tenant data encryption key
13
+
14
+ Per-tenant data encryption key (DEK) protecting documents at rest in the Atlas
15
+ object store. Each DEK is wrapped by the platform KMS root key and unwrapped
16
+ only inside the storage service; plaintext keys never leave process memory.
17
+ Rotation re-wraps the DEK on the `90d` cadence declared above without
18
+ re-encrypting historical objects, which remain readable via key versioning.
@@ -0,0 +1,27 @@
1
+ ---
2
+ id: CLAIM-001
3
+ title: "tenant_id access-token claim"
4
+ type: jwt_claim
5
+ ---
6
+ <!-- jwt_claim 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:
9
+ jwt_claim).
10
+ - A "Schema" section MUST carry a ```json code block with the claim's
11
+ JSON Schema. -->
12
+ # [CLAIM-001] tenant_id access-token claim
13
+
14
+ Custom claim stamped into every Atlas access token at issuance. The policy
15
+ engine uses it for row-level tenant isolation (see PERM-001); tokens without a
16
+ valid `tenant_id` are rejected at the gateway before reaching any service.
17
+
18
+ ## Schema
19
+
20
+ ```json
21
+ {
22
+ "title": "tenant_id",
23
+ "type": "string",
24
+ "format": "uuid",
25
+ "description": "UUID of the tenant the subject is acting within; immutable for the token lifetime"
26
+ }
27
+ ```
@@ -0,0 +1,19 @@
1
+ ---
2
+ id: MFA-001
3
+ title: "TOTP authenticator app"
4
+ type: mfa_method
5
+ kind: totp
6
+ factor: possession
7
+ ---
8
+ <!-- mfa_method authoring skeleton (spec-objects-security). Fill every part with
9
+ substantive content. Contract (manifest body_extraction asserts):
10
+ - Frontmatter MUST carry id, title, type (type:
11
+ mfa_method) and kind (the method kind); factor names the auth factor
12
+ class it provides. -->
13
+ # [MFA-001] TOTP authenticator app
14
+
15
+ Time-based one-time password second factor (RFC 6238) for Atlas operator and
16
+ break-glass accounts. Secrets are 160-bit, provisioned via QR code over an
17
+ authenticated session, and stored encrypted under KEY-001. Codes are 6 digits
18
+ on a 30-second step with one step of clock skew accepted; ten single-use
19
+ recovery codes are issued at enrollment for device-loss fallback.
@@ -0,0 +1,26 @@
1
+ ---
2
+ id: PASSPOL-001
3
+ title: "Atlas local-account password policy"
4
+ type: password_policy
5
+ ---
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.
16
+
17
+ ## Rules
18
+
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
@@ -0,0 +1,18 @@
1
+ ---
2
+ id: PERM-001
3
+ title: "Read tenant documents"
4
+ type: permission
5
+ resource: document
6
+ verb: read
7
+ ---
8
+ <!-- permission authoring skeleton (spec-objects-security). Fill every part with
9
+ 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
14
+
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.
@@ -0,0 +1,25 @@
1
+ ---
2
+ id: POL-001
3
+ title: "Access token lifetime policy"
4
+ type: policy
5
+ ---
6
+ <!-- policy 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: 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.
14
+
15
+ ## Policy
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.
@@ -0,0 +1,20 @@
1
+ ---
2
+ id: RISK-001
3
+ title: "Tenant account takeover via token theft"
4
+ type: risk
5
+ likelihood: medium
6
+ impact: high
7
+ ---
8
+ <!-- risk authoring skeleton (spec-objects-security). Fill every part with
9
+ 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
13
+
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.
@@ -0,0 +1,21 @@
1
+ ---
2
+ id: ROLE-001
3
+ title: "Tenant Auditor role"
4
+ type: role
5
+ ---
6
+ <!-- role 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: 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.
15
+
16
+ ## Permissions
17
+
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
@@ -0,0 +1,20 @@
1
+ ---
2
+ id: SCOPE-001
3
+ title: "documents:read OAuth scope"
4
+ type: scope
5
+ ---
6
+ <!-- scope 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: 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.
14
+
15
+ ## Grants
16
+
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
@@ -0,0 +1,25 @@
1
+ ---
2
+ id: SECRET-001
3
+ title: "Atlas OIDC client secret"
4
+ type: secret
5
+ ---
6
+ <!-- secret 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: 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.
16
+
17
+ ## Rotation
18
+
19
+ - Rotated every 90 days by the `secret-rotator` job, which registers a new
20
+ 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).
@@ -0,0 +1,24 @@
1
+ ---
2
+ id: SESSION-001
3
+ title: "Atlas web session configuration"
4
+ type: session_config
5
+ ---
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.
16
+
17
+ ## Settings
18
+
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
@@ -0,0 +1,19 @@
1
+ ---
2
+ id: THREAT-001
3
+ title: "Stolen refresh token replay"
4
+ type: threat
5
+ stride_category: Spoofing
6
+ vector: "exfiltrated refresh token replayed from attacker infrastructure"
7
+ ---
8
+ <!-- threat authoring skeleton (spec-objects-security). Fill every part with
9
+ 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
13
+
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.
@@ -0,0 +1,26 @@
1
+ ---
2
+ id: BOUND-001
3
+ title: "Edge to auth-service trust boundary"
4
+ type: trust_boundary
5
+ ---
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.
18
+
19
+ ## Boundary
20
+
21
+ ```mermaid
22
+ 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")]
26
+ ```
@@ -0,0 +1,21 @@
1
+ ---
2
+ id: VULN-001
3
+ title: "Session store affected by lua sandbox escape"
4
+ type: vulnerability
5
+ cve_id: CVE-2024-31449
6
+ severity: high
7
+ ---
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
14
+
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.