trustdex 0.6.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.
- package/README.ko.md +74 -6
- package/README.md +79 -7
- package/bin/trustdex.mjs +152 -9
- package/docs/ARCHITECTURE.ko.md +4 -2
- package/docs/ARCHITECTURE.md +9 -4
- package/docs/CLAUDE.ko.md +48 -2
- package/docs/CLAUDE.md +56 -5
- package/docs/CODEX.ko.md +53 -5
- package/docs/CODEX.md +63 -11
- package/docs/DEFENSIVE_USE_CASE.ko.md +2 -2
- package/docs/DEFENSIVE_USE_CASE.md +5 -3
- package/docs/EVALUATION.ko.md +29 -1
- package/docs/EVALUATION.md +28 -1
- package/docs/ROADMAP.ko.md +12 -4
- package/docs/ROADMAP.md +12 -4
- package/docs/SECURITY_RESEARCH.ko.md +2 -0
- package/docs/SECURITY_RESEARCH.md +2 -0
- package/docs/THREAT_MODEL.ko.md +5 -1
- package/docs/THREAT_MODEL.md +5 -1
- package/examples/adversarial/permissions/README.ko.md +15 -0
- package/examples/adversarial/permissions/README.md +15 -0
- package/examples/adversarial/permissions/claude-expanded.settings.json +30 -0
- package/examples/adversarial/permissions/claude-restrictive.settings.json +16 -0
- package/examples/adversarial/permissions/codex-expanded.toml +13 -0
- package/examples/adversarial/permissions/codex-restrictive.toml +13 -0
- package/examples/adversarial/permissions/expected.json +18 -0
- package/package.json +4 -3
- package/schemas/permission-posture-diff-v1.schema.json +78 -0
- package/schemas/permission-posture-diff-v2.schema.json +37 -0
- package/schemas/permission-posture-v1.schema.json +162 -0
- package/schemas/permission-posture-v2.schema.json +117 -0
- package/src/claude-settings.mjs +81 -0
- package/src/codex-settings.mjs +81 -3
- package/src/posture.mjs +601 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
approval_policy = "on-request"
|
|
2
|
+
approvals_reviewer = "user"
|
|
3
|
+
sandbox_mode = "read-only"
|
|
4
|
+
web_search = "disabled"
|
|
5
|
+
|
|
6
|
+
[sandbox_workspace_write]
|
|
7
|
+
network_access = false
|
|
8
|
+
writable_roots = []
|
|
9
|
+
exclude_slash_tmp = true
|
|
10
|
+
exclude_tmpdir_env_var = true
|
|
11
|
+
|
|
12
|
+
[agents]
|
|
13
|
+
enabled = false
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"claude": {
|
|
3
|
+
"restrictiveAction": "ask",
|
|
4
|
+
"expandedAction": "block",
|
|
5
|
+
"diffStatus": "needs-review"
|
|
6
|
+
},
|
|
7
|
+
"codex": {
|
|
8
|
+
"restrictiveAction": "ask",
|
|
9
|
+
"expandedAction": "block",
|
|
10
|
+
"diffStatus": "needs-review"
|
|
11
|
+
},
|
|
12
|
+
"forbiddenOutputMarkers": [
|
|
13
|
+
"INERT_CLAUDE_PERMISSION_SECRET",
|
|
14
|
+
"INERT_CLAUDE_PERMISSION_PATH",
|
|
15
|
+
"INERT_CLAUDE_HOOK_SECRET",
|
|
16
|
+
"INERT_CODEX_PERMISSION_PATH"
|
|
17
|
+
]
|
|
18
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trustdex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "A local-first trust, provenance, and review gate for AI agent tools, MCP servers, skills, and plugins.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "node --test",
|
|
11
|
-
"test:research": "node --test test/research-fixtures.test.mjs test/claude-settings.test.mjs test/codex-settings.test.mjs",
|
|
12
|
-
"check": "node --check bin/trustdex.mjs && node --check src/canonical.mjs && node --check src/claude-config.mjs && node --check src/claude-settings.mjs && node --check src/codex-config.mjs && node --check src/codex-settings.mjs && node --check src/extensions.mjs && node --check src/gate.mjs && node --check src/inspect.mjs && node --check src/policy.mjs && node --check src/policy-packs.mjs && node --check src/provenance.mjs && node --check src/provenance-online.mjs && node --check src/review.mjs && node --check src/snapshot.mjs && node --check src/trust-record.mjs",
|
|
11
|
+
"test:research": "node --test test/research-fixtures.test.mjs test/claude-settings.test.mjs test/codex-settings.test.mjs test/posture.test.mjs test/settings-stack.test.mjs",
|
|
12
|
+
"check": "node --check bin/trustdex.mjs && node --check src/canonical.mjs && node --check src/claude-config.mjs && node --check src/claude-settings.mjs && node --check src/codex-config.mjs && node --check src/codex-settings.mjs && node --check src/extensions.mjs && node --check src/gate.mjs && node --check src/inspect.mjs && node --check src/policy.mjs && node --check src/policy-packs.mjs && node --check src/posture.mjs && node --check src/provenance.mjs && node --check src/provenance-online.mjs && node --check src/review.mjs && node --check src/snapshot.mjs && node --check src/trust-record.mjs",
|
|
13
13
|
"prepublishOnly": "npm test && npm run check"
|
|
14
14
|
},
|
|
15
15
|
"engines": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"src",
|
|
40
40
|
"docs",
|
|
41
41
|
"examples",
|
|
42
|
+
"schemas",
|
|
42
43
|
"README.md",
|
|
43
44
|
"SECURITY.md",
|
|
44
45
|
"LICENSE"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/grigent/trustdex/blob/main/schemas/permission-posture-diff-v1.schema.json",
|
|
4
|
+
"title": "TrustDex permission posture diff v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"schemaVersion",
|
|
9
|
+
"runtime",
|
|
10
|
+
"status",
|
|
11
|
+
"baselineDigest",
|
|
12
|
+
"currentDigest",
|
|
13
|
+
"summary",
|
|
14
|
+
"changes"
|
|
15
|
+
],
|
|
16
|
+
"properties": {
|
|
17
|
+
"schemaVersion": { "const": 1 },
|
|
18
|
+
"runtime": { "enum": ["claude", "codex"] },
|
|
19
|
+
"status": { "enum": ["unchanged", "restricted", "needs-review"] },
|
|
20
|
+
"baselineDigest": { "$ref": "#/$defs/digest" },
|
|
21
|
+
"currentDigest": { "$ref": "#/$defs/digest" },
|
|
22
|
+
"summary": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"additionalProperties": false,
|
|
25
|
+
"required": ["expansions", "restrictions", "reviews"],
|
|
26
|
+
"properties": {
|
|
27
|
+
"expansions": { "$ref": "#/$defs/count" },
|
|
28
|
+
"restrictions": { "$ref": "#/$defs/count" },
|
|
29
|
+
"reviews": { "$ref": "#/$defs/count" }
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"changes": {
|
|
33
|
+
"type": "array",
|
|
34
|
+
"items": { "$ref": "#/$defs/change" }
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"$defs": {
|
|
38
|
+
"digest": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
41
|
+
},
|
|
42
|
+
"count": {
|
|
43
|
+
"type": "integer",
|
|
44
|
+
"minimum": 0
|
|
45
|
+
},
|
|
46
|
+
"scalar": {
|
|
47
|
+
"type": ["string", "number", "boolean", "null"]
|
|
48
|
+
},
|
|
49
|
+
"change": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"additionalProperties": false,
|
|
52
|
+
"required": ["impact", "type", "field"],
|
|
53
|
+
"properties": {
|
|
54
|
+
"impact": { "enum": ["expansion", "restriction", "review"] },
|
|
55
|
+
"type": {
|
|
56
|
+
"enum": [
|
|
57
|
+
"policy-changed",
|
|
58
|
+
"items-added",
|
|
59
|
+
"items-removed",
|
|
60
|
+
"decision-changed",
|
|
61
|
+
"count-changed",
|
|
62
|
+
"boolean-changed",
|
|
63
|
+
"value-changed"
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
"field": { "type": "string" },
|
|
67
|
+
"before": { "$ref": "#/$defs/scalar" },
|
|
68
|
+
"after": { "$ref": "#/$defs/scalar" },
|
|
69
|
+
"items": {
|
|
70
|
+
"type": "array",
|
|
71
|
+
"minItems": 1,
|
|
72
|
+
"uniqueItems": true,
|
|
73
|
+
"items": { "type": "string" }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/grigent/trustdex/blob/main/schemas/permission-posture-diff-v2.schema.json",
|
|
4
|
+
"title": "TrustDex merged permission posture diff v2",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"schemaVersion",
|
|
9
|
+
"runtime",
|
|
10
|
+
"status",
|
|
11
|
+
"baselineDigest",
|
|
12
|
+
"currentDigest",
|
|
13
|
+
"summary",
|
|
14
|
+
"changes"
|
|
15
|
+
],
|
|
16
|
+
"properties": {
|
|
17
|
+
"schemaVersion": { "const": 2 },
|
|
18
|
+
"runtime": { "enum": ["claude", "codex"] },
|
|
19
|
+
"status": { "enum": ["unchanged", "restricted", "needs-review"] },
|
|
20
|
+
"baselineDigest": { "$ref": "permission-posture-diff-v1.schema.json#/$defs/digest" },
|
|
21
|
+
"currentDigest": { "$ref": "permission-posture-diff-v1.schema.json#/$defs/digest" },
|
|
22
|
+
"summary": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"additionalProperties": false,
|
|
25
|
+
"required": ["expansions", "restrictions", "reviews"],
|
|
26
|
+
"properties": {
|
|
27
|
+
"expansions": { "$ref": "permission-posture-diff-v1.schema.json#/$defs/count" },
|
|
28
|
+
"restrictions": { "$ref": "permission-posture-diff-v1.schema.json#/$defs/count" },
|
|
29
|
+
"reviews": { "$ref": "permission-posture-diff-v1.schema.json#/$defs/count" }
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"changes": {
|
|
33
|
+
"type": "array",
|
|
34
|
+
"items": { "$ref": "permission-posture-diff-v1.schema.json#/$defs/change" }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/grigent/trustdex/blob/main/schemas/permission-posture-v1.schema.json",
|
|
4
|
+
"title": "TrustDex permission posture snapshot v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"schemaVersion",
|
|
9
|
+
"kind",
|
|
10
|
+
"createdAt",
|
|
11
|
+
"runtime",
|
|
12
|
+
"policyDigest",
|
|
13
|
+
"posture",
|
|
14
|
+
"digest"
|
|
15
|
+
],
|
|
16
|
+
"properties": {
|
|
17
|
+
"schemaVersion": { "const": 1 },
|
|
18
|
+
"kind": { "const": "agent-permission-posture" },
|
|
19
|
+
"createdAt": { "type": "string", "format": "date-time" },
|
|
20
|
+
"runtime": { "enum": ["claude", "codex"] },
|
|
21
|
+
"policyDigest": { "$ref": "#/$defs/digest" },
|
|
22
|
+
"digest": { "$ref": "#/$defs/digest" },
|
|
23
|
+
"posture": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"additionalProperties": false,
|
|
26
|
+
"required": ["action", "signals", "metadata"],
|
|
27
|
+
"properties": {
|
|
28
|
+
"action": { "enum": ["allow", "ask", "block"] },
|
|
29
|
+
"signals": {
|
|
30
|
+
"type": "array",
|
|
31
|
+
"uniqueItems": true,
|
|
32
|
+
"items": { "type": "string" }
|
|
33
|
+
},
|
|
34
|
+
"metadata": { "type": "object" }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"allOf": [
|
|
39
|
+
{
|
|
40
|
+
"if": { "properties": { "runtime": { "const": "claude" } } },
|
|
41
|
+
"then": {
|
|
42
|
+
"properties": {
|
|
43
|
+
"posture": {
|
|
44
|
+
"properties": {
|
|
45
|
+
"metadata": { "$ref": "#/$defs/claudeMetadata" }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"if": { "properties": { "runtime": { "const": "codex" } } },
|
|
53
|
+
"then": {
|
|
54
|
+
"properties": {
|
|
55
|
+
"posture": {
|
|
56
|
+
"properties": {
|
|
57
|
+
"metadata": { "$ref": "#/$defs/codexMetadata" }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"$defs": {
|
|
65
|
+
"digest": {
|
|
66
|
+
"type": "string",
|
|
67
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
68
|
+
},
|
|
69
|
+
"count": {
|
|
70
|
+
"type": "integer",
|
|
71
|
+
"minimum": 0
|
|
72
|
+
},
|
|
73
|
+
"claudeMetadata": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"additionalProperties": false,
|
|
76
|
+
"required": [
|
|
77
|
+
"defaultMode",
|
|
78
|
+
"allowRuleCount",
|
|
79
|
+
"askRuleCount",
|
|
80
|
+
"denyRuleCount",
|
|
81
|
+
"allowCategories",
|
|
82
|
+
"additionalDirectoryCount",
|
|
83
|
+
"bypassDisabled",
|
|
84
|
+
"autoModeDisabled",
|
|
85
|
+
"outsideReadsBlocked",
|
|
86
|
+
"uninspectedRootKeyCount"
|
|
87
|
+
],
|
|
88
|
+
"properties": {
|
|
89
|
+
"defaultMode": {
|
|
90
|
+
"type": ["string", "null"],
|
|
91
|
+
"enum": [null, "default", "manual", "acceptEdits", "plan", "dontAsk", "bypassPermissions", "auto"]
|
|
92
|
+
},
|
|
93
|
+
"allowRuleCount": { "$ref": "#/$defs/count" },
|
|
94
|
+
"askRuleCount": { "$ref": "#/$defs/count" },
|
|
95
|
+
"denyRuleCount": { "$ref": "#/$defs/count" },
|
|
96
|
+
"allowCategories": {
|
|
97
|
+
"type": "array",
|
|
98
|
+
"uniqueItems": true,
|
|
99
|
+
"items": { "enum": ["filesystem", "mcp", "network", "other", "shell", "subagent"] }
|
|
100
|
+
},
|
|
101
|
+
"additionalDirectoryCount": { "$ref": "#/$defs/count" },
|
|
102
|
+
"bypassDisabled": { "type": "boolean" },
|
|
103
|
+
"autoModeDisabled": { "type": "boolean" },
|
|
104
|
+
"outsideReadsBlocked": { "type": "boolean" },
|
|
105
|
+
"uninspectedRootKeyCount": { "$ref": "#/$defs/count" }
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
"codexMetadata": {
|
|
109
|
+
"type": "object",
|
|
110
|
+
"additionalProperties": false,
|
|
111
|
+
"required": [
|
|
112
|
+
"approvalPolicy",
|
|
113
|
+
"granularPromptCategories",
|
|
114
|
+
"approvalsReviewer",
|
|
115
|
+
"sandboxMode",
|
|
116
|
+
"defaultPermissionsKind",
|
|
117
|
+
"webSearch",
|
|
118
|
+
"networkAccess",
|
|
119
|
+
"additionalWritableRootCount",
|
|
120
|
+
"tempRootsExcluded",
|
|
121
|
+
"subagentsEnabled",
|
|
122
|
+
"uninspectedAgentKeyCount",
|
|
123
|
+
"uninspectedRootKeyCount",
|
|
124
|
+
"uninspectedSectionKindCount"
|
|
125
|
+
],
|
|
126
|
+
"properties": {
|
|
127
|
+
"approvalPolicy": {
|
|
128
|
+
"type": ["string", "null"],
|
|
129
|
+
"enum": [null, "on-request", "never", "granular"]
|
|
130
|
+
},
|
|
131
|
+
"granularPromptCategories": {
|
|
132
|
+
"type": "array",
|
|
133
|
+
"uniqueItems": true,
|
|
134
|
+
"items": { "enum": ["mcp_elicitations", "request_permissions", "rules", "sandbox_approval", "skill_approval"] }
|
|
135
|
+
},
|
|
136
|
+
"approvalsReviewer": {
|
|
137
|
+
"type": ["string", "null"],
|
|
138
|
+
"enum": [null, "user", "auto_review"]
|
|
139
|
+
},
|
|
140
|
+
"sandboxMode": {
|
|
141
|
+
"type": ["string", "null"],
|
|
142
|
+
"enum": [null, "read-only", "workspace-write", "danger-full-access"]
|
|
143
|
+
},
|
|
144
|
+
"defaultPermissionsKind": {
|
|
145
|
+
"type": ["string", "null"],
|
|
146
|
+
"enum": [null, ":read-only", ":workspace", ":danger-full-access", "custom"]
|
|
147
|
+
},
|
|
148
|
+
"webSearch": {
|
|
149
|
+
"type": ["string", "null"],
|
|
150
|
+
"enum": [null, "disabled", "cached", "indexed", "live"]
|
|
151
|
+
},
|
|
152
|
+
"networkAccess": { "type": "boolean" },
|
|
153
|
+
"additionalWritableRootCount": { "$ref": "#/$defs/count" },
|
|
154
|
+
"tempRootsExcluded": { "type": "boolean" },
|
|
155
|
+
"subagentsEnabled": { "type": "boolean" },
|
|
156
|
+
"uninspectedAgentKeyCount": { "$ref": "#/$defs/count" },
|
|
157
|
+
"uninspectedRootKeyCount": { "$ref": "#/$defs/count" },
|
|
158
|
+
"uninspectedSectionKindCount": { "$ref": "#/$defs/count" }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/grigent/trustdex/blob/main/schemas/permission-posture-v2.schema.json",
|
|
4
|
+
"title": "TrustDex merged permission posture snapshot v2",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"schemaVersion",
|
|
9
|
+
"kind",
|
|
10
|
+
"createdAt",
|
|
11
|
+
"runtime",
|
|
12
|
+
"policyDigest",
|
|
13
|
+
"posture",
|
|
14
|
+
"sources",
|
|
15
|
+
"digest"
|
|
16
|
+
],
|
|
17
|
+
"properties": {
|
|
18
|
+
"schemaVersion": { "const": 2 },
|
|
19
|
+
"kind": { "const": "agent-permission-posture" },
|
|
20
|
+
"createdAt": { "type": "string", "format": "date-time" },
|
|
21
|
+
"runtime": { "enum": ["claude", "codex"] },
|
|
22
|
+
"policyDigest": { "$ref": "permission-posture-v1.schema.json#/$defs/digest" },
|
|
23
|
+
"digest": { "$ref": "permission-posture-v1.schema.json#/$defs/digest" },
|
|
24
|
+
"posture": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"additionalProperties": false,
|
|
27
|
+
"required": ["action", "signals", "metadata"],
|
|
28
|
+
"properties": {
|
|
29
|
+
"action": { "enum": ["allow", "ask", "block"] },
|
|
30
|
+
"signals": {
|
|
31
|
+
"type": "array",
|
|
32
|
+
"uniqueItems": true,
|
|
33
|
+
"items": { "type": "string" }
|
|
34
|
+
},
|
|
35
|
+
"metadata": { "type": "object" }
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"sources": { "type": "object" }
|
|
39
|
+
},
|
|
40
|
+
"allOf": [
|
|
41
|
+
{
|
|
42
|
+
"if": { "properties": { "runtime": { "const": "claude" } } },
|
|
43
|
+
"then": {
|
|
44
|
+
"properties": {
|
|
45
|
+
"posture": {
|
|
46
|
+
"properties": {
|
|
47
|
+
"metadata": { "$ref": "permission-posture-v1.schema.json#/$defs/claudeMetadata" }
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"sources": { "$ref": "#/$defs/claudeSources" }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"if": { "properties": { "runtime": { "const": "codex" } } },
|
|
56
|
+
"then": {
|
|
57
|
+
"properties": {
|
|
58
|
+
"posture": {
|
|
59
|
+
"properties": {
|
|
60
|
+
"metadata": { "$ref": "permission-posture-v1.schema.json#/$defs/codexMetadata" }
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"sources": { "$ref": "#/$defs/codexSources" }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"$defs": {
|
|
69
|
+
"count": {
|
|
70
|
+
"type": "integer",
|
|
71
|
+
"minimum": 0
|
|
72
|
+
},
|
|
73
|
+
"claudeSources": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"additionalProperties": false,
|
|
76
|
+
"required": ["sourceCount", "activeSourceCount", "sourceCounts"],
|
|
77
|
+
"properties": {
|
|
78
|
+
"sourceCount": { "$ref": "#/$defs/count" },
|
|
79
|
+
"activeSourceCount": { "$ref": "#/$defs/count" },
|
|
80
|
+
"sourceCounts": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"additionalProperties": false,
|
|
83
|
+
"required": ["user", "project", "local", "session", "managed"],
|
|
84
|
+
"properties": {
|
|
85
|
+
"user": { "$ref": "#/$defs/count" },
|
|
86
|
+
"project": { "$ref": "#/$defs/count" },
|
|
87
|
+
"local": { "$ref": "#/$defs/count" },
|
|
88
|
+
"session": { "$ref": "#/$defs/count" },
|
|
89
|
+
"managed": { "$ref": "#/$defs/count" }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"codexSources": {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"additionalProperties": false,
|
|
97
|
+
"required": ["sourceCount", "activeSourceCount", "sourceCounts"],
|
|
98
|
+
"properties": {
|
|
99
|
+
"sourceCount": { "$ref": "#/$defs/count" },
|
|
100
|
+
"activeSourceCount": { "$ref": "#/$defs/count" },
|
|
101
|
+
"sourceCounts": {
|
|
102
|
+
"type": "object",
|
|
103
|
+
"additionalProperties": false,
|
|
104
|
+
"required": ["system", "managed", "user", "profile", "project", "cli"],
|
|
105
|
+
"properties": {
|
|
106
|
+
"system": { "$ref": "#/$defs/count" },
|
|
107
|
+
"managed": { "$ref": "#/$defs/count" },
|
|
108
|
+
"user": { "$ref": "#/$defs/count" },
|
|
109
|
+
"profile": { "$ref": "#/$defs/count" },
|
|
110
|
+
"project": { "$ref": "#/$defs/count" },
|
|
111
|
+
"cli": { "$ref": "#/$defs/count" }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
package/src/claude-settings.mjs
CHANGED
|
@@ -19,6 +19,14 @@ const MODES = new Set([
|
|
|
19
19
|
'auto'
|
|
20
20
|
]);
|
|
21
21
|
const NON_PERMISSION_ROOT_KEYS = new Set(['$schema', 'permissions']);
|
|
22
|
+
const CLAUDE_SCOPE_RANK = Object.freeze({
|
|
23
|
+
user: 0,
|
|
24
|
+
project: 1,
|
|
25
|
+
local: 2,
|
|
26
|
+
session: 3,
|
|
27
|
+
managed: 4
|
|
28
|
+
});
|
|
29
|
+
const MERGED_PERMISSION_LISTS = new Set(['allow', 'ask', 'deny', 'additionalDirectories']);
|
|
22
30
|
|
|
23
31
|
function isObject(value) {
|
|
24
32
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -171,3 +179,76 @@ export function inspectClaudeSettings(settings, sourceName = 'settings.json') {
|
|
|
171
179
|
envKeys
|
|
172
180
|
};
|
|
173
181
|
}
|
|
182
|
+
|
|
183
|
+
function normalizeClaudeLayers(layers) {
|
|
184
|
+
if (!Array.isArray(layers) || !layers.length) {
|
|
185
|
+
throw new Error('Claude settings stack requires at least one settings source.');
|
|
186
|
+
}
|
|
187
|
+
const seen = new Set();
|
|
188
|
+
return layers.map((layer, index) => {
|
|
189
|
+
if (!isObject(layer) || !Object.hasOwn(CLAUDE_SCOPE_RANK, layer.scope)) {
|
|
190
|
+
throw new Error('Unsupported Claude settings source scope.');
|
|
191
|
+
}
|
|
192
|
+
if (seen.has(layer.scope)) {
|
|
193
|
+
throw new Error(`Claude settings stack accepts only one ${layer.scope} source.`);
|
|
194
|
+
}
|
|
195
|
+
seen.add(layer.scope);
|
|
196
|
+
if (!isObject(layer.settings)) throw new Error('Claude settings source must contain a JSON object.');
|
|
197
|
+
inspectClaudeSettings(layer.settings, 'Claude settings source');
|
|
198
|
+
return { ...layer, index };
|
|
199
|
+
}).sort((left, right) =>
|
|
200
|
+
CLAUDE_SCOPE_RANK[left.scope] - CLAUDE_SCOPE_RANK[right.scope] || left.index - right.index
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function mergeClaudeSettings(layers) {
|
|
205
|
+
const merged = {};
|
|
206
|
+
const permissions = {};
|
|
207
|
+
|
|
208
|
+
for (const { scope, settings } of layers) {
|
|
209
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
210
|
+
if (key === 'permissions') continue;
|
|
211
|
+
if (Array.isArray(value) && Array.isArray(merged[key])) merged[key] = [...merged[key], ...value];
|
|
212
|
+
else merged[key] = value;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const current = settings.permissions || {};
|
|
216
|
+
for (const [key, value] of Object.entries(current)) {
|
|
217
|
+
if (MERGED_PERMISSION_LISTS.has(key)) {
|
|
218
|
+
permissions[key] = [...(permissions[key] || []), ...value];
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (key === 'defaultMode' && ['project', 'local'].includes(scope) &&
|
|
222
|
+
['auto', 'bypassPermissions'].includes(value)) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
permissions[key] = value;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (Object.keys(permissions).length) merged.permissions = permissions;
|
|
230
|
+
return merged;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function inspectClaudeSettingsStack(layers) {
|
|
234
|
+
const normalized = normalizeClaudeLayers(layers);
|
|
235
|
+
const merged = mergeClaudeSettings(normalized);
|
|
236
|
+
const result = inspectClaudeSettings(merged, 'merged Claude settings');
|
|
237
|
+
const sourceCounts = Object.fromEntries(Object.keys(CLAUDE_SCOPE_RANK).map((scope) => [scope, 0]));
|
|
238
|
+
let activeSourceCount = 0;
|
|
239
|
+
for (const layer of normalized) {
|
|
240
|
+
sourceCounts[layer.scope] += 1;
|
|
241
|
+
if (Object.keys(layer.settings).length) activeSourceCount += 1;
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
...result,
|
|
245
|
+
name: 'merged Claude settings',
|
|
246
|
+
kind: 'claude-settings-stack',
|
|
247
|
+
source: { type: 'local', format: 'claude-settings-stack', scopes: sourceCounts },
|
|
248
|
+
settingsContext: {
|
|
249
|
+
sourceCount: normalized.length,
|
|
250
|
+
activeSourceCount,
|
|
251
|
+
sourceCounts
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}
|
package/src/codex-settings.mjs
CHANGED
|
@@ -25,6 +25,14 @@ const INSPECTED_ROOT_KEYS = new Set([
|
|
|
25
25
|
'default_permissions',
|
|
26
26
|
'web_search'
|
|
27
27
|
]);
|
|
28
|
+
const CODEX_SCOPE_RANK = Object.freeze({
|
|
29
|
+
system: 0,
|
|
30
|
+
managed: 1,
|
|
31
|
+
user: 2,
|
|
32
|
+
profile: 3,
|
|
33
|
+
project: 4,
|
|
34
|
+
cli: 5
|
|
35
|
+
});
|
|
28
36
|
|
|
29
37
|
function isObject(value) {
|
|
30
38
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -154,8 +162,7 @@ function validateSandbox(sandbox) {
|
|
|
154
162
|
}
|
|
155
163
|
}
|
|
156
164
|
|
|
157
|
-
|
|
158
|
-
const { root, sandbox, agents, sections } = parseTomlAssignments(text);
|
|
165
|
+
function inspectParsedCodexSettings({ root, sandbox, agents, sections }, sourceName, kind) {
|
|
159
166
|
validateSandbox(sandbox);
|
|
160
167
|
|
|
161
168
|
const signals = new Set();
|
|
@@ -222,7 +229,7 @@ export function inspectCodexSettingsText(text, sourceName = 'config.toml') {
|
|
|
222
229
|
|
|
223
230
|
return {
|
|
224
231
|
name: sourceName,
|
|
225
|
-
kind
|
|
232
|
+
kind,
|
|
226
233
|
source: { type: 'local', format: 'codex-settings', path: sourceName },
|
|
227
234
|
metadata: {
|
|
228
235
|
approvalPolicy: approval.mode,
|
|
@@ -245,3 +252,74 @@ export function inspectCodexSettingsText(text, sourceName = 'config.toml') {
|
|
|
245
252
|
envKeys: []
|
|
246
253
|
};
|
|
247
254
|
}
|
|
255
|
+
|
|
256
|
+
export function inspectCodexSettingsText(text, sourceName = 'config.toml') {
|
|
257
|
+
return inspectParsedCodexSettings(
|
|
258
|
+
parseTomlAssignments(text),
|
|
259
|
+
sourceName,
|
|
260
|
+
'codex-settings'
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function normalizeCodexLayers(layers) {
|
|
265
|
+
if (!Array.isArray(layers) || !layers.length) {
|
|
266
|
+
throw new Error('Codex settings stack requires at least one config source.');
|
|
267
|
+
}
|
|
268
|
+
const seen = new Set();
|
|
269
|
+
return layers.map((layer, index) => {
|
|
270
|
+
if (!isObject(layer) || !Object.hasOwn(CODEX_SCOPE_RANK, layer.scope)) {
|
|
271
|
+
throw new Error('Unsupported Codex config source scope.');
|
|
272
|
+
}
|
|
273
|
+
if (layer.scope !== 'project' && seen.has(layer.scope)) {
|
|
274
|
+
throw new Error(`Codex settings stack accepts only one ${layer.scope} source.`);
|
|
275
|
+
}
|
|
276
|
+
seen.add(layer.scope);
|
|
277
|
+
if (typeof layer.text !== 'string') throw new Error('Codex config source must contain TOML text.');
|
|
278
|
+
const parsed = parseTomlAssignments(layer.text);
|
|
279
|
+
inspectParsedCodexSettings(parsed, 'Codex settings source', 'codex-settings');
|
|
280
|
+
return { ...layer, parsed, index };
|
|
281
|
+
}).sort((left, right) =>
|
|
282
|
+
CODEX_SCOPE_RANK[left.scope] - CODEX_SCOPE_RANK[right.scope] || left.index - right.index
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function inspectCodexSettingsStack(layers) {
|
|
287
|
+
const normalized = normalizeCodexLayers(layers);
|
|
288
|
+
const merged = {
|
|
289
|
+
root: Object.create(null),
|
|
290
|
+
sandbox: Object.create(null),
|
|
291
|
+
agents: Object.create(null),
|
|
292
|
+
sections: []
|
|
293
|
+
};
|
|
294
|
+
const sections = new Set();
|
|
295
|
+
const sourceCounts = Object.fromEntries(Object.keys(CODEX_SCOPE_RANK).map((scope) => [scope, 0]));
|
|
296
|
+
let activeSourceCount = 0;
|
|
297
|
+
|
|
298
|
+
for (const layer of normalized) {
|
|
299
|
+
sourceCounts[layer.scope] += 1;
|
|
300
|
+
Object.assign(merged.root, layer.parsed.root);
|
|
301
|
+
Object.assign(merged.sandbox, layer.parsed.sandbox);
|
|
302
|
+
Object.assign(merged.agents, layer.parsed.agents);
|
|
303
|
+
for (const section of layer.parsed.sections) sections.add(section);
|
|
304
|
+
if (Object.keys(layer.parsed.root).length || Object.keys(layer.parsed.sandbox).length ||
|
|
305
|
+
Object.keys(layer.parsed.agents).length || layer.parsed.sections.length) {
|
|
306
|
+
activeSourceCount += 1;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
merged.sections = [...sections].sort();
|
|
310
|
+
|
|
311
|
+
const result = inspectParsedCodexSettings(
|
|
312
|
+
merged,
|
|
313
|
+
'merged Codex settings',
|
|
314
|
+
'codex-settings-stack'
|
|
315
|
+
);
|
|
316
|
+
return {
|
|
317
|
+
...result,
|
|
318
|
+
source: { type: 'local', format: 'codex-settings-stack', scopes: sourceCounts },
|
|
319
|
+
settingsContext: {
|
|
320
|
+
sourceCount: normalized.length,
|
|
321
|
+
activeSourceCount,
|
|
322
|
+
sourceCounts
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
}
|