trustdex 0.7.0 → 0.9.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 +71 -8
- package/README.md +75 -8
- package/bin/trustdex.mjs +204 -11
- package/docs/ARCHITECTURE.ko.md +10 -2
- package/docs/ARCHITECTURE.md +15 -4
- package/docs/CLAUDE.ko.md +47 -2
- package/docs/CLAUDE.md +54 -5
- package/docs/CODEX.ko.md +49 -5
- package/docs/CODEX.md +59 -11
- package/docs/DEFENSIVE_USE_CASE.ko.md +8 -2
- package/docs/DEFENSIVE_USE_CASE.md +11 -3
- package/docs/EVALUATION.ko.md +29 -1
- package/docs/EVALUATION.md +29 -1
- package/docs/ROADMAP.ko.md +7 -4
- package/docs/ROADMAP.md +7 -4
- package/docs/THREAT_MODEL.ko.md +7 -1
- package/docs/THREAT_MODEL.md +7 -1
- package/examples/adversarial/README.ko.md +2 -0
- package/examples/adversarial/README.md +2 -0
- package/examples/adversarial/bindings/README.ko.md +24 -0
- package/examples/adversarial/bindings/README.md +25 -0
- package/examples/adversarial/bindings/claude.mcp.json +12 -0
- package/examples/adversarial/bindings/claude.settings.json +9 -0
- package/examples/adversarial/bindings/codex.toml +9 -0
- package/examples/adversarial/bindings/expected.json +15 -0
- package/examples/adversarial/bindings/trust-store.json +15 -0
- package/package.json +3 -3
- package/schemas/permission-posture-diff-v2.schema.json +37 -0
- package/schemas/permission-posture-v2.schema.json +117 -0
- package/schemas/tool-permission-bindings-diff-v1.schema.json +70 -0
- package/schemas/tool-permission-bindings-v1.schema.json +94 -0
- package/src/bindings.mjs +354 -0
- package/src/claude-settings.mjs +130 -0
- package/src/codex-settings.mjs +81 -3
- package/src/posture.mjs +69 -8
package/src/posture.mjs
CHANGED
|
@@ -2,7 +2,8 @@ import { sha256Json } from './canonical.mjs';
|
|
|
2
2
|
|
|
3
3
|
const ACTION_RANK = Object.freeze({ allow: 0, ask: 1, block: 2 });
|
|
4
4
|
const SNAPSHOT_KIND = 'agent-permission-posture';
|
|
5
|
-
const
|
|
5
|
+
const SINGLE_SNAPSHOT_VERSION = 1;
|
|
6
|
+
const STACK_SNAPSHOT_VERSION = 2;
|
|
6
7
|
|
|
7
8
|
const CLAUDE_MODES = new Set([
|
|
8
9
|
null,
|
|
@@ -66,6 +67,10 @@ const RUNTIME_SIGNALS = Object.freeze({
|
|
|
66
67
|
'uninspected-codex-sections'
|
|
67
68
|
])
|
|
68
69
|
});
|
|
70
|
+
const SOURCE_SCOPES = Object.freeze({
|
|
71
|
+
claude: Object.freeze(['user', 'project', 'local', 'session', 'managed']),
|
|
72
|
+
codex: Object.freeze(['system', 'managed', 'user', 'profile', 'project', 'cli'])
|
|
73
|
+
});
|
|
69
74
|
|
|
70
75
|
function requireObject(value, message) {
|
|
71
76
|
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(message);
|
|
@@ -202,14 +207,36 @@ function projectPolicy(policy, runtime) {
|
|
|
202
207
|
};
|
|
203
208
|
}
|
|
204
209
|
|
|
210
|
+
function normalizeSettingsContext(context, runtime) {
|
|
211
|
+
requireObject(context, 'Permission posture settings context is missing.');
|
|
212
|
+
assertMetadataKeys(context, new Set(['sourceCount', 'activeSourceCount', 'sourceCounts']));
|
|
213
|
+
const sourceCount = requireCount(context.sourceCount, 'sourceCount');
|
|
214
|
+
const activeSourceCount = requireCount(context.activeSourceCount, 'activeSourceCount');
|
|
215
|
+
if (activeSourceCount > sourceCount) {
|
|
216
|
+
throw new Error('Permission posture active source count exceeds total source count.');
|
|
217
|
+
}
|
|
218
|
+
const inputCounts = requireObject(context.sourceCounts, 'Permission posture source counts are missing.');
|
|
219
|
+
const scopes = SOURCE_SCOPES[runtime];
|
|
220
|
+
assertMetadataKeys(inputCounts, new Set(scopes));
|
|
221
|
+
const sourceCounts = Object.fromEntries(
|
|
222
|
+
scopes.map((scope) => [scope, requireCount(inputCounts[scope], `sourceCounts.${scope}`)])
|
|
223
|
+
);
|
|
224
|
+
if (Object.values(sourceCounts).reduce((total, count) => total + count, 0) !== sourceCount) {
|
|
225
|
+
throw new Error('Permission posture source counts do not match the total source count.');
|
|
226
|
+
}
|
|
227
|
+
return { sourceCount, activeSourceCount, sourceCounts };
|
|
228
|
+
}
|
|
229
|
+
|
|
205
230
|
function snapshotDigest(snapshot) {
|
|
206
|
-
|
|
231
|
+
const projected = {
|
|
207
232
|
schemaVersion: snapshot.schemaVersion,
|
|
208
233
|
kind: snapshot.kind,
|
|
209
234
|
runtime: snapshot.runtime,
|
|
210
235
|
policyDigest: snapshot.policyDigest,
|
|
211
236
|
posture: snapshot.posture
|
|
212
|
-
}
|
|
237
|
+
};
|
|
238
|
+
if (snapshot.schemaVersion === STACK_SNAPSHOT_VERSION) projected.sources = snapshot.sources;
|
|
239
|
+
return sha256Json(projected);
|
|
213
240
|
}
|
|
214
241
|
|
|
215
242
|
export function createPermissionPostureSnapshot(result, policy = {}) {
|
|
@@ -218,9 +245,9 @@ export function createPermissionPostureSnapshot(result, policy = {}) {
|
|
|
218
245
|
if (!Array.isArray(result.signals) || result.signals.some((item) => typeof item !== 'string')) {
|
|
219
246
|
throw new Error('Permission posture result has invalid signals.');
|
|
220
247
|
}
|
|
221
|
-
const runtime =
|
|
248
|
+
const runtime = ['claude-settings', 'claude-settings-stack'].includes(result.kind)
|
|
222
249
|
? 'claude'
|
|
223
|
-
:
|
|
250
|
+
: ['codex-settings', 'codex-settings-stack'].includes(result.kind) ? 'codex' : null;
|
|
224
251
|
if (!runtime) throw new Error('Permission posture supports Claude or Codex settings results only.');
|
|
225
252
|
if (result.signals.some((signal) => !RUNTIME_SIGNALS[runtime].has(signal))) {
|
|
226
253
|
throw new Error('Permission posture result contains an unsupported signal.');
|
|
@@ -234,20 +261,23 @@ export function createPermissionPostureSnapshot(result, policy = {}) {
|
|
|
234
261
|
? normalizeClaudeMetadata(metadata)
|
|
235
262
|
: normalizeCodexMetadata(metadata)
|
|
236
263
|
};
|
|
264
|
+
const isStack = result.kind.endsWith('-stack');
|
|
237
265
|
const snapshot = {
|
|
238
|
-
schemaVersion:
|
|
266
|
+
schemaVersion: isStack ? STACK_SNAPSHOT_VERSION : SINGLE_SNAPSHOT_VERSION,
|
|
239
267
|
kind: SNAPSHOT_KIND,
|
|
240
268
|
createdAt: new Date().toISOString(),
|
|
241
269
|
runtime,
|
|
242
270
|
policyDigest: sha256Json(projectPolicy(policy, runtime)),
|
|
243
271
|
posture
|
|
244
272
|
};
|
|
273
|
+
if (isStack) snapshot.sources = normalizeSettingsContext(result.settingsContext, runtime);
|
|
245
274
|
return { ...snapshot, digest: snapshotDigest(snapshot) };
|
|
246
275
|
}
|
|
247
276
|
|
|
248
277
|
function validateSnapshot(snapshot) {
|
|
249
278
|
requireObject(snapshot, 'Invalid permission posture snapshot.');
|
|
250
|
-
if (snapshot.schemaVersion
|
|
279
|
+
if (![SINGLE_SNAPSHOT_VERSION, STACK_SNAPSHOT_VERSION].includes(snapshot.schemaVersion) ||
|
|
280
|
+
snapshot.kind !== SNAPSHOT_KIND) {
|
|
251
281
|
throw new Error('Unsupported permission posture snapshot schema.');
|
|
252
282
|
}
|
|
253
283
|
if (!['claude', 'codex'].includes(snapshot.runtime)) throw new Error('Invalid permission posture runtime.');
|
|
@@ -272,6 +302,14 @@ function validateSnapshot(snapshot) {
|
|
|
272
302
|
const normalized = normalizeCodexSnapshotMetadata(metadata);
|
|
273
303
|
if (sha256Json(normalized) !== sha256Json(metadata)) throw new Error('Non-canonical Codex posture metadata.');
|
|
274
304
|
}
|
|
305
|
+
if (snapshot.schemaVersion === STACK_SNAPSHOT_VERSION) {
|
|
306
|
+
const normalized = normalizeSettingsContext(snapshot.sources, snapshot.runtime);
|
|
307
|
+
if (sha256Json(normalized) !== sha256Json(snapshot.sources)) {
|
|
308
|
+
throw new Error('Non-canonical permission posture source metadata.');
|
|
309
|
+
}
|
|
310
|
+
} else if (snapshot.sources !== undefined) {
|
|
311
|
+
throw new Error('Permission posture v1 does not support source metadata.');
|
|
312
|
+
}
|
|
275
313
|
if (snapshot.digest !== snapshotDigest(snapshot)) throw new Error('Permission posture snapshot digest mismatch.');
|
|
276
314
|
return snapshot;
|
|
277
315
|
}
|
|
@@ -496,6 +534,29 @@ export function diffPermissionPostureSnapshots(beforeInput, afterInput) {
|
|
|
496
534
|
if (before.runtime !== after.runtime) throw new Error('Cannot compare different permission posture runtimes.');
|
|
497
535
|
|
|
498
536
|
const changes = [];
|
|
537
|
+
if (before.schemaVersion !== after.schemaVersion) {
|
|
538
|
+
addChange(
|
|
539
|
+
changes,
|
|
540
|
+
'review',
|
|
541
|
+
'value-changed',
|
|
542
|
+
'schemaVersion',
|
|
543
|
+
before.schemaVersion,
|
|
544
|
+
after.schemaVersion
|
|
545
|
+
);
|
|
546
|
+
} else if (before.schemaVersion === STACK_SNAPSHOT_VERSION) {
|
|
547
|
+
for (const field of ['sourceCount', 'activeSourceCount']) {
|
|
548
|
+
if (before.sources[field] !== after.sources[field]) {
|
|
549
|
+
addChange(changes, 'review', 'count-changed', `sources.${field}`, before.sources[field], after.sources[field]);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
for (const scope of SOURCE_SCOPES[before.runtime]) {
|
|
553
|
+
const previous = before.sources.sourceCounts[scope];
|
|
554
|
+
const current = after.sources.sourceCounts[scope];
|
|
555
|
+
if (previous !== current) {
|
|
556
|
+
addChange(changes, 'review', 'count-changed', `sources.${scope}`, previous, current);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
499
560
|
if (before.policyDigest !== after.policyDigest) {
|
|
500
561
|
changes.push({ impact: 'review', type: 'policy-changed', field: 'policyDigest' });
|
|
501
562
|
}
|
|
@@ -529,7 +590,7 @@ export function diffPermissionPostureSnapshots(beforeInput, afterInput) {
|
|
|
529
590
|
? 'needs-review'
|
|
530
591
|
: summary.restrictions ? 'restricted' : 'unchanged';
|
|
531
592
|
return {
|
|
532
|
-
schemaVersion:
|
|
593
|
+
schemaVersion: Math.max(before.schemaVersion, after.schemaVersion),
|
|
533
594
|
runtime: before.runtime,
|
|
534
595
|
status,
|
|
535
596
|
baselineDigest: before.digest,
|