trustdex 0.5.0 → 0.6.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 +391 -0
- package/README.md +39 -7
- package/SECURITY.md +4 -0
- package/bin/trustdex.mjs +24 -1
- package/docs/ARCHITECTURE.ko.md +52 -0
- package/docs/ARCHITECTURE.md +12 -1
- package/docs/CLAUDE.ko.md +122 -0
- package/docs/CLAUDE.md +50 -1
- package/docs/CODEX.ko.md +98 -0
- package/docs/CODEX.md +41 -4
- package/docs/DEFENSIVE_USE_CASE.ko.md +85 -0
- package/docs/DEFENSIVE_USE_CASE.md +89 -0
- package/docs/EVALUATION.ko.md +67 -0
- package/docs/EVALUATION.md +36 -1
- package/docs/RELEASING.ko.md +70 -0
- package/docs/RELEASING.md +4 -0
- package/docs/ROADMAP.ko.md +46 -0
- package/docs/ROADMAP.md +14 -2
- package/docs/SECURITY_RESEARCH.ko.md +69 -0
- package/docs/SECURITY_RESEARCH.md +9 -0
- package/docs/THREAT_MODEL.ko.md +56 -0
- package/docs/THREAT_MODEL.md +10 -1
- package/docs/TRUST_STORE.ko.md +93 -0
- package/docs/TRUST_STORE.md +4 -0
- package/examples/adversarial/README.ko.md +31 -0
- package/examples/adversarial/README.md +31 -0
- package/examples/adversarial/drift-after.json +11 -0
- package/examples/adversarial/drift-before.json +8 -0
- package/examples/adversarial/expected.json +30 -0
- package/examples/adversarial/mcp-config.json +32 -0
- package/examples/adversarial/policy.json +10 -0
- package/examples/claude.mcp.json +24 -0
- package/examples/claude.policy.json +6 -0
- package/examples/claude.settings.json +26 -0
- package/examples/codex.config.toml +16 -0
- package/examples/codex.permissions.toml +13 -0
- package/examples/mcp.json +22 -0
- package/examples/trust-store.example.json +16 -0
- package/examples/trustdex.policy.json +14 -0
- package/package.json +4 -3
- package/src/claude-settings.mjs +173 -0
- package/src/codex-config.mjs +9 -7
- package/src/codex-settings.mjs +247 -0
- package/src/inspect.mjs +60 -3
- package/src/policy-packs.mjs +38 -2
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"trusted-pinned-package": {
|
|
3
|
+
"action": "allow",
|
|
4
|
+
"signals": ["install-on-run"]
|
|
5
|
+
},
|
|
6
|
+
"floating-version": {
|
|
7
|
+
"action": "block",
|
|
8
|
+
"signals": ["install-on-run", "unbounded-version"]
|
|
9
|
+
},
|
|
10
|
+
"shell-launcher": {
|
|
11
|
+
"action": "block",
|
|
12
|
+
"signals": ["filesystem-path", "shell-execution"]
|
|
13
|
+
},
|
|
14
|
+
"credential-bearing-url": {
|
|
15
|
+
"action": "block",
|
|
16
|
+
"signals": ["embedded-credentials", "network-endpoint", "url-query-parameters"]
|
|
17
|
+
},
|
|
18
|
+
"insecure-remote": {
|
|
19
|
+
"action": "block",
|
|
20
|
+
"signals": ["insecure-transport", "network-endpoint"]
|
|
21
|
+
},
|
|
22
|
+
"sensitive-local-env": {
|
|
23
|
+
"action": "ask",
|
|
24
|
+
"signals": ["filesystem-path", "secret-env"]
|
|
25
|
+
},
|
|
26
|
+
"unknown-remote": {
|
|
27
|
+
"action": "block",
|
|
28
|
+
"signals": ["network-endpoint"]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"trusted-pinned-package": {
|
|
4
|
+
"command": "npx",
|
|
5
|
+
"args": ["-y", "@trustdex-fixtures/reviewed-server@1.2.3"]
|
|
6
|
+
},
|
|
7
|
+
"floating-version": {
|
|
8
|
+
"command": "npx",
|
|
9
|
+
"args": ["-y", "@trustdex-fixtures/floating-server@latest"]
|
|
10
|
+
},
|
|
11
|
+
"shell-launcher": {
|
|
12
|
+
"command": "bash",
|
|
13
|
+
"args": ["-lc", "./fixture-server.mjs"]
|
|
14
|
+
},
|
|
15
|
+
"credential-bearing-url": {
|
|
16
|
+
"url": "https://fixture-user:fixture-password@credentials.fixture.invalid/mcp?token=fixture-token"
|
|
17
|
+
},
|
|
18
|
+
"insecure-remote": {
|
|
19
|
+
"url": "http://insecure.fixture.invalid/mcp"
|
|
20
|
+
},
|
|
21
|
+
"sensitive-local-env": {
|
|
22
|
+
"command": "node",
|
|
23
|
+
"args": ["./fixture-server.mjs"],
|
|
24
|
+
"env": {
|
|
25
|
+
"SERVICE_API_TOKEN": "NOT_A_REAL_SECRET"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"unknown-remote": {
|
|
29
|
+
"url": "https://unknown.fixture.invalid/mcp"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"reviewed": {
|
|
4
|
+
"type": "http",
|
|
5
|
+
"url": "https://reviewed.invalid/mcp"
|
|
6
|
+
},
|
|
7
|
+
"unknown": {
|
|
8
|
+
"type": "http",
|
|
9
|
+
"url": "https://unknown.invalid/mcp"
|
|
10
|
+
},
|
|
11
|
+
"helper": {
|
|
12
|
+
"type": "http",
|
|
13
|
+
"url": "https://reviewed.invalid/mcp",
|
|
14
|
+
"headersHelper": "INERT_HELPER_NOT_EXECUTED"
|
|
15
|
+
},
|
|
16
|
+
"needsReview": {
|
|
17
|
+
"type": "http",
|
|
18
|
+
"url": "https://reviewed.invalid/mcp",
|
|
19
|
+
"headers": {
|
|
20
|
+
"Authorization": "Bearer ${EXAMPLE_API_TOKEN}"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
|
3
|
+
"permissions": {
|
|
4
|
+
"allow": [
|
|
5
|
+
"Bash(npm test)",
|
|
6
|
+
"WebFetch(domain:docs.example.invalid)",
|
|
7
|
+
"mcp__reviewed__read_*",
|
|
8
|
+
"Agent(Explore)"
|
|
9
|
+
],
|
|
10
|
+
"ask": [
|
|
11
|
+
"Edit",
|
|
12
|
+
"Bash(git push *)"
|
|
13
|
+
],
|
|
14
|
+
"deny": [
|
|
15
|
+
"Read(./.env)",
|
|
16
|
+
"WebFetch(domain:*)"
|
|
17
|
+
],
|
|
18
|
+
"defaultMode": "default",
|
|
19
|
+
"additionalDirectories": [
|
|
20
|
+
"../shared-read-only"
|
|
21
|
+
],
|
|
22
|
+
"disableBypassPermissionsMode": "disable",
|
|
23
|
+
"disableAutoMode": "disable",
|
|
24
|
+
"blockReadsOutsideWorkingDirectories": true
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
model = "gpt-5.6-sol"
|
|
2
|
+
|
|
3
|
+
[mcp_servers.filesystem]
|
|
4
|
+
command = "npx"
|
|
5
|
+
args = ["-y", "@modelcontextprotocol/server-filesystem@1.2.3", "./workspace"]
|
|
6
|
+
env_vars = ["LOCAL_TOKEN"]
|
|
7
|
+
|
|
8
|
+
[mcp_servers.remote-example]
|
|
9
|
+
url = "https://mcp.example.com/mcp"
|
|
10
|
+
bearer_token_env_var = "MCP_BEARER_TOKEN"
|
|
11
|
+
|
|
12
|
+
[mcp_servers.remote-example.http_headers]
|
|
13
|
+
X-Region = "us-east-1"
|
|
14
|
+
|
|
15
|
+
[features]
|
|
16
|
+
example = true
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
approval_policy = "on-request"
|
|
2
|
+
approvals_reviewer = "user"
|
|
3
|
+
sandbox_mode = "workspace-write"
|
|
4
|
+
web_search = "cached"
|
|
5
|
+
|
|
6
|
+
[sandbox_workspace_write]
|
|
7
|
+
network_access = false
|
|
8
|
+
writable_roots = ["../reviewed-shared"]
|
|
9
|
+
exclude_slash_tmp = true
|
|
10
|
+
exclude_tmpdir_env_var = true
|
|
11
|
+
|
|
12
|
+
[agents]
|
|
13
|
+
enabled = false
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"filesystem": {
|
|
4
|
+
"command": "npx",
|
|
5
|
+
"args": [
|
|
6
|
+
"-y",
|
|
7
|
+
"@modelcontextprotocol/server-filesystem@1.2.3",
|
|
8
|
+
"./workspace"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"unknown-calendar": {
|
|
12
|
+
"command": "npx",
|
|
13
|
+
"args": [
|
|
14
|
+
"-y",
|
|
15
|
+
"calendar-helper@latest"
|
|
16
|
+
],
|
|
17
|
+
"env": {
|
|
18
|
+
"CALENDAR_API_TOKEN": "${CALENDAR_API_TOKEN}"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"claims": [
|
|
4
|
+
{
|
|
5
|
+
"type": "package",
|
|
6
|
+
"subject": "example-mcp-server",
|
|
7
|
+
"publisher": "Example Publisher",
|
|
8
|
+
"status": "verified",
|
|
9
|
+
"evidence": {
|
|
10
|
+
"kind": "manual-review",
|
|
11
|
+
"reference": "https://example.invalid/security-review",
|
|
12
|
+
"checkedAt": "2026-09-19T00:00:00Z"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"unknownAction": "block",
|
|
4
|
+
"localAction": "ask",
|
|
5
|
+
"sensitiveAction": "ask",
|
|
6
|
+
"unpinnedAction": "ask",
|
|
7
|
+
"shellAction": "block",
|
|
8
|
+
"trustedPackages": [
|
|
9
|
+
"@modelcontextprotocol/server-filesystem"
|
|
10
|
+
],
|
|
11
|
+
"allowedRemoteHosts": [
|
|
12
|
+
"mcp.example.com"
|
|
13
|
+
]
|
|
14
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trustdex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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",
|
|
12
|
-
"check": "node --check bin/trustdex.mjs && node --check src/canonical.mjs && node --check src/claude-config.mjs && node --check src/codex-config.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",
|
|
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",
|
|
13
13
|
"prepublishOnly": "npm test && npm run check"
|
|
14
14
|
},
|
|
15
15
|
"engines": {
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"bin",
|
|
39
39
|
"src",
|
|
40
40
|
"docs",
|
|
41
|
+
"examples",
|
|
41
42
|
"README.md",
|
|
42
43
|
"SECURITY.md",
|
|
43
44
|
"LICENSE"
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
const SECRET_KEY_RE = /(token|secret|password|passwd|api[_-]?key|credential|auth)/i;
|
|
2
|
+
const PERMISSION_KEYS = new Set([
|
|
3
|
+
'allow',
|
|
4
|
+
'ask',
|
|
5
|
+
'deny',
|
|
6
|
+
'defaultMode',
|
|
7
|
+
'additionalDirectories',
|
|
8
|
+
'disableBypassPermissionsMode',
|
|
9
|
+
'disableAutoMode',
|
|
10
|
+
'blockReadsOutsideWorkingDirectories'
|
|
11
|
+
]);
|
|
12
|
+
const MODES = new Set([
|
|
13
|
+
'default',
|
|
14
|
+
'manual',
|
|
15
|
+
'acceptEdits',
|
|
16
|
+
'plan',
|
|
17
|
+
'dontAsk',
|
|
18
|
+
'bypassPermissions',
|
|
19
|
+
'auto'
|
|
20
|
+
]);
|
|
21
|
+
const NON_PERMISSION_ROOT_KEYS = new Set(['$schema', 'permissions']);
|
|
22
|
+
|
|
23
|
+
function isObject(value) {
|
|
24
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function requireStringArray(value, field) {
|
|
28
|
+
if (value === undefined) return [];
|
|
29
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== 'string' || !item.trim())) {
|
|
30
|
+
throw new Error(`Claude permissions.${field} must be an array of non-empty strings.`);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseRule(rule, list) {
|
|
36
|
+
const value = rule.trim();
|
|
37
|
+
const match = value.match(/^([A-Za-z0-9_*.-]+)(?:\(([\s\S]*)\))?$/);
|
|
38
|
+
if (!match) throw new Error(`Invalid Claude permissions.${list} rule syntax.`);
|
|
39
|
+
|
|
40
|
+
const tool = match[1];
|
|
41
|
+
const specifier = match[2] === undefined ? null : match[2];
|
|
42
|
+
if (specifier !== null && !specifier.trim()) {
|
|
43
|
+
throw new Error(`Invalid empty specifier in Claude permissions.${list}.`);
|
|
44
|
+
}
|
|
45
|
+
if (list === 'allow' && (tool === '*' || tool === 'mcp__*' || (!tool.startsWith('mcp__') && tool.includes('*')))) {
|
|
46
|
+
throw new Error('Claude ignores unanchored wildcard allow rules; use an explicit tool or MCP server rule.');
|
|
47
|
+
}
|
|
48
|
+
if (tool.startsWith('mcp__') && specifier !== null) {
|
|
49
|
+
throw new Error('Claude skips MCP permission rules with parenthesized specifiers.');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
tool,
|
|
54
|
+
hasSpecifier: specifier !== null,
|
|
55
|
+
matchesAll: specifier === null || specifier.trim() === '*' || specifier.trim() === 'domain:*',
|
|
56
|
+
mcpWildcard: tool.startsWith('mcp__') && (tool.endsWith('__*') || !tool.includes('__', 5))
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function toolCategory(tool) {
|
|
61
|
+
if (tool === 'Bash' || tool === 'PowerShell') return 'shell';
|
|
62
|
+
if (['Read', 'Edit', 'Write', 'Glob', 'Grep', 'NotebookEdit'].includes(tool)) return 'filesystem';
|
|
63
|
+
if (tool === 'WebFetch' || tool === 'WebSearch') return 'network';
|
|
64
|
+
if (tool === 'Agent') return 'subagent';
|
|
65
|
+
if (tool.startsWith('mcp__')) return 'mcp';
|
|
66
|
+
return 'other';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function validateDisable(value, field) {
|
|
70
|
+
if (value !== undefined && value !== 'disable') {
|
|
71
|
+
throw new Error(`Claude permissions.${field} must be "disable" when present.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function validatePermissions(value) {
|
|
76
|
+
if (value === undefined) return { permissions: {}, rules: { allow: [], ask: [], deny: [] } };
|
|
77
|
+
if (!isObject(value)) throw new Error('Claude settings permissions must be a JSON object.');
|
|
78
|
+
|
|
79
|
+
for (const key of Object.keys(value)) {
|
|
80
|
+
if (!PERMISSION_KEYS.has(key)) throw new Error(`Unsupported Claude permission setting: ${key}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const defaultMode = value.defaultMode;
|
|
84
|
+
if (defaultMode !== undefined && (!MODES.has(defaultMode) || typeof defaultMode !== 'string')) {
|
|
85
|
+
throw new Error('Unsupported Claude permissions.defaultMode value.');
|
|
86
|
+
}
|
|
87
|
+
const additionalDirectories = requireStringArray(value.additionalDirectories, 'additionalDirectories');
|
|
88
|
+
if (value.blockReadsOutsideWorkingDirectories !== undefined &&
|
|
89
|
+
typeof value.blockReadsOutsideWorkingDirectories !== 'boolean') {
|
|
90
|
+
throw new Error('Claude permissions.blockReadsOutsideWorkingDirectories must be a boolean.');
|
|
91
|
+
}
|
|
92
|
+
validateDisable(value.disableBypassPermissionsMode, 'disableBypassPermissionsMode');
|
|
93
|
+
validateDisable(value.disableAutoMode, 'disableAutoMode');
|
|
94
|
+
|
|
95
|
+
const rules = {};
|
|
96
|
+
for (const list of ['allow', 'ask', 'deny']) {
|
|
97
|
+
rules[list] = requireStringArray(value[list], list).map((rule) => parseRule(rule, list));
|
|
98
|
+
}
|
|
99
|
+
return { permissions: { ...value, additionalDirectories }, rules };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function inspectAllowRules(rules, signals) {
|
|
103
|
+
for (const rule of rules) {
|
|
104
|
+
signals.add('claude-auto-approve');
|
|
105
|
+
const category = toolCategory(rule.tool);
|
|
106
|
+
if (category === 'shell') signals.add('claude-shell-auto-approve');
|
|
107
|
+
if (category === 'filesystem') signals.add('claude-filesystem-auto-approve');
|
|
108
|
+
if (category === 'network') signals.add('claude-network-auto-approve');
|
|
109
|
+
if (category === 'subagent') signals.add('claude-subagent-auto-approve');
|
|
110
|
+
if (category === 'mcp') signals.add('claude-mcp-auto-approve');
|
|
111
|
+
if (rule.matchesAll && ['shell', 'filesystem', 'network', 'subagent'].includes(category)) {
|
|
112
|
+
signals.add('claude-broad-auto-approve');
|
|
113
|
+
}
|
|
114
|
+
if (rule.mcpWildcard) signals.add('claude-mcp-wildcard');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function inspectClaudeSettings(settings, sourceName = 'settings.json') {
|
|
119
|
+
if (!isObject(settings)) throw new Error('Expected a Claude settings JSON object.');
|
|
120
|
+
|
|
121
|
+
const { permissions, rules } = validatePermissions(settings.permissions);
|
|
122
|
+
const signals = new Set();
|
|
123
|
+
inspectAllowRules(rules.allow, signals);
|
|
124
|
+
|
|
125
|
+
if (permissions.defaultMode === 'bypassPermissions') signals.add('claude-bypass-permissions');
|
|
126
|
+
if (permissions.defaultMode === 'auto') signals.add('claude-auto-mode');
|
|
127
|
+
if (permissions.defaultMode === 'acceptEdits') signals.add('claude-filesystem-auto-approve');
|
|
128
|
+
if (permissions.additionalDirectories?.length) signals.add('claude-additional-directories');
|
|
129
|
+
if (permissions.blockReadsOutsideWorkingDirectories === false) signals.add('claude-outside-read-access');
|
|
130
|
+
|
|
131
|
+
let envKeys = [];
|
|
132
|
+
if (settings.env !== undefined) {
|
|
133
|
+
if (!isObject(settings.env)) throw new Error('Claude settings env must be a JSON object.');
|
|
134
|
+
envKeys = Object.keys(settings.env).sort();
|
|
135
|
+
signals.add('claude-environment');
|
|
136
|
+
if (envKeys.some((key) => SECRET_KEY_RE.test(key))) signals.add('secret-env');
|
|
137
|
+
}
|
|
138
|
+
if (settings.hooks !== undefined) signals.add('claude-hooks');
|
|
139
|
+
if (settings.apiKeyHelper !== undefined) signals.add('claude-credential-helper');
|
|
140
|
+
if (settings.sandbox !== undefined) signals.add('claude-sandbox-settings');
|
|
141
|
+
if (settings.enabledPlugins !== undefined || settings.extraKnownMarketplaces !== undefined) {
|
|
142
|
+
signals.add('claude-extension-loading');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const inspectedAdjacentKeys = new Set([
|
|
146
|
+
'env', 'hooks', 'apiKeyHelper', 'sandbox', 'enabledPlugins', 'extraKnownMarketplaces'
|
|
147
|
+
]);
|
|
148
|
+
const uninspectedRootKeys = Object.keys(settings)
|
|
149
|
+
.filter((key) => !NON_PERMISSION_ROOT_KEYS.has(key) && !inspectedAdjacentKeys.has(key))
|
|
150
|
+
.sort();
|
|
151
|
+
if (uninspectedRootKeys.length) signals.add('uninspected-claude-settings');
|
|
152
|
+
|
|
153
|
+
const allowCategories = [...new Set(rules.allow.map((rule) => toolCategory(rule.tool)))].sort();
|
|
154
|
+
return {
|
|
155
|
+
name: sourceName,
|
|
156
|
+
kind: 'claude-settings',
|
|
157
|
+
source: { type: 'local', format: 'claude-settings', path: sourceName },
|
|
158
|
+
metadata: {
|
|
159
|
+
defaultMode: permissions.defaultMode || null,
|
|
160
|
+
allowRuleCount: rules.allow.length,
|
|
161
|
+
askRuleCount: rules.ask.length,
|
|
162
|
+
denyRuleCount: rules.deny.length,
|
|
163
|
+
allowCategories,
|
|
164
|
+
additionalDirectoryCount: permissions.additionalDirectories?.length || 0,
|
|
165
|
+
bypassDisabled: permissions.disableBypassPermissionsMode === 'disable',
|
|
166
|
+
autoModeDisabled: permissions.disableAutoMode === 'disable',
|
|
167
|
+
outsideReadsBlocked: permissions.blockReadsOutsideWorkingDirectories === true,
|
|
168
|
+
uninspectedRootKeys
|
|
169
|
+
},
|
|
170
|
+
signals: [...signals].sort(),
|
|
171
|
+
envKeys
|
|
172
|
+
};
|
|
173
|
+
}
|
package/src/codex-config.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const CODEX_MCP_NESTED_SECTIONS = new Set(['env', 'http_headers', 'env_http_headers']);
|
|
2
2
|
|
|
3
|
-
function stripComment(line) {
|
|
3
|
+
export function stripComment(line) {
|
|
4
4
|
let quote = null;
|
|
5
5
|
let escaped = false;
|
|
6
6
|
for (let i = 0; i < line.length; i += 1) {
|
|
@@ -61,7 +61,7 @@ function splitTopLevel(value) {
|
|
|
61
61
|
return out;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
function unquote(value) {
|
|
64
|
+
export function unquote(value) {
|
|
65
65
|
const raw = value.trim();
|
|
66
66
|
if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
|
|
67
67
|
return raw.slice(1, -1);
|
|
@@ -69,7 +69,7 @@ function unquote(value) {
|
|
|
69
69
|
return raw;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
function parseValue(raw) {
|
|
72
|
+
export function parseValue(raw) {
|
|
73
73
|
const value = raw.trim();
|
|
74
74
|
if (!value) return '';
|
|
75
75
|
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
@@ -79,7 +79,7 @@ function parseValue(raw) {
|
|
|
79
79
|
return splitTopLevel(value.slice(1, -1)).map(parseValue);
|
|
80
80
|
}
|
|
81
81
|
if (value.startsWith('{') && value.endsWith('}')) {
|
|
82
|
-
const object =
|
|
82
|
+
const object = Object.create(null);
|
|
83
83
|
for (const part of splitTopLevel(value.slice(1, -1))) {
|
|
84
84
|
const index = part.indexOf('=');
|
|
85
85
|
if (index < 1) throw new Error(`Unsupported TOML inline table entry: ${part}`);
|
|
@@ -93,7 +93,7 @@ function parseValue(raw) {
|
|
|
93
93
|
return value;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
function valueComplete(value) {
|
|
96
|
+
export function valueComplete(value) {
|
|
97
97
|
let square = 0;
|
|
98
98
|
let curly = 0;
|
|
99
99
|
let quote = null;
|
|
@@ -128,8 +128,10 @@ export function codexServerNameFromHeader(header) {
|
|
|
128
128
|
/^mcp_servers\.(?:"([^"]+)"|'([^']+)'|([A-Za-z0-9_-]+))(?:\.(.*))?$/
|
|
129
129
|
);
|
|
130
130
|
if (!match) return null;
|
|
131
|
+
const name = match[1] || match[2] || match[3];
|
|
132
|
+
if (['__proto__', 'prototype', 'constructor'].includes(name)) return null;
|
|
131
133
|
return {
|
|
132
|
-
name
|
|
134
|
+
name,
|
|
133
135
|
nested: match[4] || ''
|
|
134
136
|
};
|
|
135
137
|
}
|
|
@@ -209,7 +211,7 @@ function applyAssignment(server, nested, key, value) {
|
|
|
209
211
|
|
|
210
212
|
export function parseCodexMcpToml(text) {
|
|
211
213
|
const lines = String(text || '').split(/\r?\n/);
|
|
212
|
-
const servers =
|
|
214
|
+
const servers = Object.create(null);
|
|
213
215
|
let section = null;
|
|
214
216
|
|
|
215
217
|
for (let i = 0; i < lines.length; i += 1) {
|