@sanctuary-framework/mcp-server 0.8.0 → 0.10.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/dist/cli.cjs +6334 -1915
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +6339 -1920
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1579 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +385 -4
- package/dist/index.d.ts +385 -4
- package/dist/index.js +1575 -72
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -15,8 +15,8 @@ var index_js$1 = require('@modelcontextprotocol/sdk/server/index.js');
|
|
|
15
15
|
var types_js = require('@modelcontextprotocol/sdk/types.js');
|
|
16
16
|
var http = require('http');
|
|
17
17
|
var https = require('https');
|
|
18
|
-
var fs = require('fs');
|
|
19
18
|
var child_process = require('child_process');
|
|
19
|
+
var fs = require('fs');
|
|
20
20
|
var index_js = require('@modelcontextprotocol/sdk/client/index.js');
|
|
21
21
|
var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
|
|
22
22
|
var sse_js = require('@modelcontextprotocol/sdk/client/sse.js');
|
|
@@ -383,6 +383,48 @@ var init_identity = __esm({
|
|
|
383
383
|
init_random();
|
|
384
384
|
}
|
|
385
385
|
});
|
|
386
|
+
async function tightenStoragePermissions(root) {
|
|
387
|
+
try {
|
|
388
|
+
await tightenEntry(root);
|
|
389
|
+
} catch {
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
async function tightenEntry(path$1) {
|
|
393
|
+
let info;
|
|
394
|
+
try {
|
|
395
|
+
info = await promises.stat(path$1);
|
|
396
|
+
} catch {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (info.isDirectory()) {
|
|
400
|
+
const current = info.mode & 511;
|
|
401
|
+
if (current !== 448) {
|
|
402
|
+
try {
|
|
403
|
+
await promises.chmod(path$1, 448);
|
|
404
|
+
} catch (err) {
|
|
405
|
+
console.error(` Warning: could not chmod dir ${path$1}: ${err.message}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
let entries;
|
|
409
|
+
try {
|
|
410
|
+
entries = await promises.readdir(path$1);
|
|
411
|
+
} catch {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
for (const entry of entries) {
|
|
415
|
+
await tightenEntry(path.join(path$1, entry));
|
|
416
|
+
}
|
|
417
|
+
} else if (info.isFile()) {
|
|
418
|
+
const current = info.mode & 511;
|
|
419
|
+
if (current !== 384) {
|
|
420
|
+
try {
|
|
421
|
+
await promises.chmod(path$1, 384);
|
|
422
|
+
} catch (err) {
|
|
423
|
+
console.error(` Warning: could not chmod file ${path$1}: ${err.message}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
386
428
|
var require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
387
429
|
var { version: PKG_VERSION } = require2("../package.json");
|
|
388
430
|
var SANCTUARY_VERSION = PKG_VERSION;
|
|
@@ -776,17 +818,48 @@ var RESERVED_NAMESPACE_PREFIXES = [
|
|
|
776
818
|
"_sovereignty_profile",
|
|
777
819
|
"_context_gate_policies"
|
|
778
820
|
];
|
|
779
|
-
var StateStore = class {
|
|
821
|
+
var StateStore = class _StateStore {
|
|
780
822
|
storage;
|
|
781
823
|
masterKey;
|
|
782
824
|
// Cache of version numbers per namespace/key for anti-rollback
|
|
783
825
|
versionCache = /* @__PURE__ */ new Map();
|
|
784
826
|
// Cache of content hashes per namespace for Merkle tree computation
|
|
785
827
|
contentHashes = /* @__PURE__ */ new Map();
|
|
828
|
+
// LRU-with-TTL cache for derived namespace keys (avoids repeated HKDF)
|
|
829
|
+
namespaceKeyCache = /* @__PURE__ */ new Map();
|
|
830
|
+
static KEY_CACHE_TTL_MS = 15 * 60 * 1e3;
|
|
831
|
+
// 15 minutes
|
|
832
|
+
static KEY_CACHE_MAX_ENTRIES = 128;
|
|
786
833
|
constructor(storage, masterKey) {
|
|
787
834
|
this.storage = storage;
|
|
788
835
|
this.masterKey = masterKey;
|
|
789
836
|
}
|
|
837
|
+
/**
|
|
838
|
+
* Get or derive a namespace encryption key, with caching.
|
|
839
|
+
* Cache entries expire after 15 minutes and are evicted LRU when
|
|
840
|
+
* the cache exceeds 128 entries.
|
|
841
|
+
*/
|
|
842
|
+
getNamespaceKey(namespace) {
|
|
843
|
+
const now = Date.now();
|
|
844
|
+
const cached = this.namespaceKeyCache.get(namespace);
|
|
845
|
+
if (cached && cached.expiresAt > now) {
|
|
846
|
+
return cached.key;
|
|
847
|
+
}
|
|
848
|
+
if (this.namespaceKeyCache.size >= _StateStore.KEY_CACHE_MAX_ENTRIES) {
|
|
849
|
+
const firstKey = this.namespaceKeyCache.keys().next().value;
|
|
850
|
+
if (firstKey !== void 0) this.namespaceKeyCache.delete(firstKey);
|
|
851
|
+
}
|
|
852
|
+
const derived = deriveNamespaceKey(this.masterKey, namespace);
|
|
853
|
+
this.namespaceKeyCache.set(namespace, {
|
|
854
|
+
key: derived,
|
|
855
|
+
expiresAt: now + _StateStore.KEY_CACHE_TTL_MS
|
|
856
|
+
});
|
|
857
|
+
return derived;
|
|
858
|
+
}
|
|
859
|
+
/** Invalidate all cached namespace keys (call on master key rotation). */
|
|
860
|
+
invalidateKeyCache() {
|
|
861
|
+
this.namespaceKeyCache.clear();
|
|
862
|
+
}
|
|
790
863
|
versionKey(namespace, key) {
|
|
791
864
|
return `${namespace}/${key}`;
|
|
792
865
|
}
|
|
@@ -828,7 +901,7 @@ var StateStore = class {
|
|
|
828
901
|
* @param options - Optional metadata
|
|
829
902
|
*/
|
|
830
903
|
async write(namespace, key, value, identityId, encryptedPrivateKey, identityEncryptionKey, options = {}) {
|
|
831
|
-
const namespaceKey =
|
|
904
|
+
const namespaceKey = this.getNamespaceKey(namespace);
|
|
832
905
|
const plaintext = stringToBytes(value);
|
|
833
906
|
const integrityHash = hashToString(plaintext);
|
|
834
907
|
const payload = encrypt(plaintext, namespaceKey);
|
|
@@ -909,7 +982,7 @@ var StateStore = class {
|
|
|
909
982
|
);
|
|
910
983
|
}
|
|
911
984
|
}
|
|
912
|
-
const namespaceKey =
|
|
985
|
+
const namespaceKey = this.getNamespaceKey(namespace);
|
|
913
986
|
const plaintext = decrypt(stateEntry.payload, namespaceKey);
|
|
914
987
|
const value = bytesToString(plaintext);
|
|
915
988
|
const computedHash = hashToString(plaintext);
|
|
@@ -1431,6 +1504,19 @@ var IdentityManager = class {
|
|
|
1431
1504
|
key_protection: si.key_protection
|
|
1432
1505
|
}));
|
|
1433
1506
|
}
|
|
1507
|
+
/** List identities with rotation count (for dashboard display). */
|
|
1508
|
+
listWithRotationCount() {
|
|
1509
|
+
return Array.from(this.identities.values()).map((si) => ({
|
|
1510
|
+
identity_id: si.identity_id,
|
|
1511
|
+
label: si.label,
|
|
1512
|
+
public_key: si.public_key,
|
|
1513
|
+
did: si.did,
|
|
1514
|
+
created_at: si.created_at,
|
|
1515
|
+
key_type: si.key_type,
|
|
1516
|
+
key_protection: si.key_protection,
|
|
1517
|
+
rotation_count: si.rotation_history?.length ?? 0
|
|
1518
|
+
}));
|
|
1519
|
+
}
|
|
1434
1520
|
};
|
|
1435
1521
|
function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog) {
|
|
1436
1522
|
const identityMgr = new IdentityManager(storage, masterKey);
|
|
@@ -1885,14 +1971,21 @@ function createL1Tools(stateStore, storage, masterKey, keyProtection, auditLog)
|
|
|
1885
1971
|
// src/l2-operational/audit-log.ts
|
|
1886
1972
|
init_encryption();
|
|
1887
1973
|
init_encoding();
|
|
1974
|
+
var DEFAULT_MAX_TOTAL_SIZE_BYTES = 100 * 1024 * 1024;
|
|
1975
|
+
var DEFAULT_MAX_ENTRIES = 1e5;
|
|
1888
1976
|
var AuditLog = class {
|
|
1889
1977
|
storage;
|
|
1890
1978
|
encryptionKey;
|
|
1891
1979
|
entries = [];
|
|
1892
1980
|
counter = 0;
|
|
1893
|
-
|
|
1981
|
+
maxTotalSizeBytes;
|
|
1982
|
+
maxEntries;
|
|
1983
|
+
rotationInFlight = false;
|
|
1984
|
+
constructor(storage, masterKey, config) {
|
|
1894
1985
|
this.storage = storage;
|
|
1895
1986
|
this.encryptionKey = derivePurposeKey(masterKey, "audit-log");
|
|
1987
|
+
this.maxTotalSizeBytes = config?.maxTotalSizeBytes ?? DEFAULT_MAX_TOTAL_SIZE_BYTES;
|
|
1988
|
+
this.maxEntries = config?.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
1896
1989
|
}
|
|
1897
1990
|
/**
|
|
1898
1991
|
* Append an audit entry.
|
|
@@ -1919,6 +2012,38 @@ var AuditLog = class {
|
|
|
1919
2012
|
key,
|
|
1920
2013
|
stringToBytes(JSON.stringify(encrypted))
|
|
1921
2014
|
);
|
|
2015
|
+
this.maybeRotate().catch(() => {
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
/**
|
|
2019
|
+
* Prune oldest audit entries when storage exceeds configured limits.
|
|
2020
|
+
* Entries are sorted by key (timestamp-based) so oldest are pruned first.
|
|
2021
|
+
*/
|
|
2022
|
+
async maybeRotate() {
|
|
2023
|
+
if (this.rotationInFlight) return;
|
|
2024
|
+
this.rotationInFlight = true;
|
|
2025
|
+
try {
|
|
2026
|
+
const metas = await this.storage.list("_audit");
|
|
2027
|
+
if (metas.length === 0) return;
|
|
2028
|
+
metas.sort((a, b) => a.key.localeCompare(b.key));
|
|
2029
|
+
const totalSize = metas.reduce((sum, m) => sum + m.size_bytes, 0);
|
|
2030
|
+
let toDelete = 0;
|
|
2031
|
+
if (metas.length > this.maxEntries) {
|
|
2032
|
+
toDelete = metas.length - this.maxEntries;
|
|
2033
|
+
}
|
|
2034
|
+
if (totalSize > this.maxTotalSizeBytes) {
|
|
2035
|
+
let runningSize = totalSize;
|
|
2036
|
+
for (let i = toDelete; i < metas.length && runningSize > this.maxTotalSizeBytes; i++) {
|
|
2037
|
+
runningSize -= metas[i].size_bytes;
|
|
2038
|
+
toDelete = i + 1;
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
for (let i = 0; i < toDelete; i++) {
|
|
2042
|
+
await this.storage.delete("_audit", metas[i].key);
|
|
2043
|
+
}
|
|
2044
|
+
} finally {
|
|
2045
|
+
this.rotationInFlight = false;
|
|
2046
|
+
}
|
|
1922
2047
|
}
|
|
1923
2048
|
/**
|
|
1924
2049
|
* Query the audit log with filtering.
|
|
@@ -2000,7 +2125,9 @@ function verifyCommitment(commitment, value, blindingFactor) {
|
|
|
2000
2125
|
const valueBytes = stringToBytes(value);
|
|
2001
2126
|
const combined = concatBytes(valueBytes, blindingBytes);
|
|
2002
2127
|
const expectedHash = toBase64url(hash(combined));
|
|
2003
|
-
|
|
2128
|
+
const commitmentBytes = fromBase64url(commitment);
|
|
2129
|
+
const expectedBytes = fromBase64url(expectedHash);
|
|
2130
|
+
return constantTimeEqual(commitmentBytes, expectedBytes);
|
|
2004
2131
|
}
|
|
2005
2132
|
var CommitmentStore = class {
|
|
2006
2133
|
storage;
|
|
@@ -3182,6 +3309,51 @@ var ReputationStore = class {
|
|
|
3182
3309
|
);
|
|
3183
3310
|
return guarantee;
|
|
3184
3311
|
}
|
|
3312
|
+
// ─── L4 Evidence Summary ─────────────────────────────────────────────
|
|
3313
|
+
/**
|
|
3314
|
+
* Summarize attestations for the L4 degradation emitter and dashboard widget.
|
|
3315
|
+
*
|
|
3316
|
+
* Returns aggregate evidence about the identity's reputation state —
|
|
3317
|
+
* counts, tier distribution, recency, dispute counts, context coverage —
|
|
3318
|
+
* without exposing raw attestations. The caller combines this with an
|
|
3319
|
+
* audit-log check for Verascore link state to produce the final
|
|
3320
|
+
* `L4Evidence` struct consumed by the SHR generator.
|
|
3321
|
+
*
|
|
3322
|
+
* @param participantDid - If provided, only count attestations where the
|
|
3323
|
+
* `participant_did` matches. If omitted, covers all attestations in the
|
|
3324
|
+
* store.
|
|
3325
|
+
*/
|
|
3326
|
+
async summarizeForSHR(participantDid) {
|
|
3327
|
+
const all = await this.loadAll();
|
|
3328
|
+
const filtered = participantDid ? all.filter((a) => a.attestation.data.participant_did === participantDid) : all;
|
|
3329
|
+
const tierDist = {
|
|
3330
|
+
"verified-sovereign": 0,
|
|
3331
|
+
"verified-degraded": 0,
|
|
3332
|
+
"self-attested": 0,
|
|
3333
|
+
"unverified": 0
|
|
3334
|
+
};
|
|
3335
|
+
const contextBreakdown = {};
|
|
3336
|
+
let mostRecentMs = null;
|
|
3337
|
+
let disputeCount = 0;
|
|
3338
|
+
for (const a of filtered) {
|
|
3339
|
+
const tier = a.attestation.data.sovereignty_tier;
|
|
3340
|
+
if (tier) tierDist[tier]++;
|
|
3341
|
+
const ctx = a.attestation.data.context;
|
|
3342
|
+
if (ctx) contextBreakdown[ctx] = (contextBreakdown[ctx] ?? 0) + 1;
|
|
3343
|
+
const ts = new Date(a.attestation.data.timestamp).getTime();
|
|
3344
|
+
if (!isNaN(ts) && (mostRecentMs === null || ts > mostRecentMs)) {
|
|
3345
|
+
mostRecentMs = ts;
|
|
3346
|
+
}
|
|
3347
|
+
if (a.attestation.data.outcome_result === "disputed") disputeCount++;
|
|
3348
|
+
}
|
|
3349
|
+
return {
|
|
3350
|
+
attestation_count: filtered.length,
|
|
3351
|
+
tier_distribution: tierDist,
|
|
3352
|
+
most_recent_attestation_at: mostRecentMs !== null ? new Date(mostRecentMs).toISOString() : null,
|
|
3353
|
+
dispute_count: disputeCount,
|
|
3354
|
+
context_breakdown: contextBreakdown
|
|
3355
|
+
};
|
|
3356
|
+
}
|
|
3185
3357
|
// ─── Tier-Aware Access ───────────────────────────────────────────────
|
|
3186
3358
|
/**
|
|
3187
3359
|
* Load attestations for tier-weighted scoring.
|
|
@@ -3203,21 +3375,37 @@ var ReputationStore = class {
|
|
|
3203
3375
|
// ─── Internal ─────────────────────────────────────────────────────────
|
|
3204
3376
|
async loadAll() {
|
|
3205
3377
|
const results = [];
|
|
3378
|
+
for await (const page of this.loadAllPaginated(100)) {
|
|
3379
|
+
results.push(...page);
|
|
3380
|
+
}
|
|
3381
|
+
return results;
|
|
3382
|
+
}
|
|
3383
|
+
/**
|
|
3384
|
+
* Cursor-based async iterator that loads attestations in pages.
|
|
3385
|
+
* Prevents OOM at 100K+ records by reading and decrypting in batches.
|
|
3386
|
+
*/
|
|
3387
|
+
async *loadAllPaginated(pageSize = 100) {
|
|
3388
|
+
let entries;
|
|
3206
3389
|
try {
|
|
3207
|
-
|
|
3208
|
-
|
|
3390
|
+
entries = await this.storage.list("_reputation");
|
|
3391
|
+
} catch {
|
|
3392
|
+
return;
|
|
3393
|
+
}
|
|
3394
|
+
for (let i = 0; i < entries.length; i += pageSize) {
|
|
3395
|
+
const page = [];
|
|
3396
|
+
const slice = entries.slice(i, i + pageSize);
|
|
3397
|
+
for (const meta of slice) {
|
|
3209
3398
|
const raw = await this.storage.read("_reputation", meta.key);
|
|
3210
3399
|
if (!raw) continue;
|
|
3211
3400
|
try {
|
|
3212
3401
|
const encrypted = JSON.parse(bytesToString(raw));
|
|
3213
3402
|
const decrypted = decrypt(encrypted, this.encryptionKey);
|
|
3214
|
-
|
|
3403
|
+
page.push(JSON.parse(bytesToString(decrypted)));
|
|
3215
3404
|
} catch {
|
|
3216
3405
|
}
|
|
3217
3406
|
}
|
|
3218
|
-
|
|
3407
|
+
if (page.length > 0) yield page;
|
|
3219
3408
|
}
|
|
3220
|
-
return results;
|
|
3221
3409
|
}
|
|
3222
3410
|
};
|
|
3223
3411
|
|
|
@@ -4444,13 +4632,78 @@ function canonicalizeForSigning(body) {
|
|
|
4444
4632
|
init_identity();
|
|
4445
4633
|
init_encoding();
|
|
4446
4634
|
var DEFAULT_VALIDITY_MS = 60 * 60 * 1e3;
|
|
4635
|
+
var DEFAULT_FRESHNESS_WINDOW_DAYS = 30;
|
|
4636
|
+
var DEFAULT_LOW_TIER_DOMINANCE_THRESHOLD = 0.6;
|
|
4637
|
+
function deriveL4Degradations(evidence, now = /* @__PURE__ */ new Date()) {
|
|
4638
|
+
const out = [];
|
|
4639
|
+
const freshnessDays = evidence.thresholds?.freshness_window_days ?? DEFAULT_FRESHNESS_WINDOW_DAYS;
|
|
4640
|
+
const dominanceThreshold = evidence.thresholds?.low_tier_dominance_threshold ?? DEFAULT_LOW_TIER_DOMINANCE_THRESHOLD;
|
|
4641
|
+
if (evidence.attestation_count === 0) {
|
|
4642
|
+
out.push({
|
|
4643
|
+
layer: "l4",
|
|
4644
|
+
code: "NO_REPUTATION_HISTORY",
|
|
4645
|
+
severity: "warning",
|
|
4646
|
+
description: "Signing identity has no recorded reputation attestations",
|
|
4647
|
+
mitigation: "Complete interactions that produce reputation_record calls, or import a portable reputation bundle"
|
|
4648
|
+
});
|
|
4649
|
+
} else {
|
|
4650
|
+
const lowTierCount = (evidence.tier_distribution["self-attested"] ?? 0) + (evidence.tier_distribution["unverified"] ?? 0);
|
|
4651
|
+
const lowTierShare = lowTierCount / evidence.attestation_count;
|
|
4652
|
+
if (lowTierShare > dominanceThreshold) {
|
|
4653
|
+
const pct = Math.round(lowTierShare * 100);
|
|
4654
|
+
out.push({
|
|
4655
|
+
layer: "l4",
|
|
4656
|
+
code: "LOW_TIER_DOMINANCE",
|
|
4657
|
+
severity: "info",
|
|
4658
|
+
description: `${pct}% of attestations are self-attested or unverified`,
|
|
4659
|
+
mitigation: "Complete sovereignty handshakes with counterparties to upgrade future attestations to verified tiers"
|
|
4660
|
+
});
|
|
4661
|
+
}
|
|
4662
|
+
if (evidence.most_recent_attestation_at) {
|
|
4663
|
+
const mostRecentMs = new Date(evidence.most_recent_attestation_at).getTime();
|
|
4664
|
+
if (!isNaN(mostRecentMs)) {
|
|
4665
|
+
const ageMs = now.getTime() - mostRecentMs;
|
|
4666
|
+
const windowMs = freshnessDays * 24 * 60 * 60 * 1e3;
|
|
4667
|
+
if (ageMs > windowMs) {
|
|
4668
|
+
const ageDays = Math.round(ageMs / (24 * 60 * 60 * 1e3));
|
|
4669
|
+
out.push({
|
|
4670
|
+
layer: "l4",
|
|
4671
|
+
code: "STALE_REPUTATION",
|
|
4672
|
+
severity: "info",
|
|
4673
|
+
description: `Most recent attestation is ${ageDays} days old (freshness window: ${freshnessDays} days)`,
|
|
4674
|
+
mitigation: "Record a fresh interaction outcome or refresh reputation from active counterparties"
|
|
4675
|
+
});
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
if (evidence.dispute_count > 0) {
|
|
4680
|
+
out.push({
|
|
4681
|
+
layer: "l4",
|
|
4682
|
+
code: "DISPUTE_ON_RECORD",
|
|
4683
|
+
severity: "warning",
|
|
4684
|
+
description: `${evidence.dispute_count} attestation${evidence.dispute_count === 1 ? "" : "s"} marked as disputed`,
|
|
4685
|
+
mitigation: "Review disputed interactions; counterparties may weigh this signal when evaluating trust"
|
|
4686
|
+
});
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
if (!evidence.verascore_linked) {
|
|
4690
|
+
out.push({
|
|
4691
|
+
layer: "l4",
|
|
4692
|
+
code: "NO_VERASCORE_LINK",
|
|
4693
|
+
severity: "info",
|
|
4694
|
+
description: "No successful reputation_publish call for this identity \u2014 reputation is not externally discoverable",
|
|
4695
|
+
mitigation: "Run reputation_publish to link this identity to a Verascore profile"
|
|
4696
|
+
});
|
|
4697
|
+
}
|
|
4698
|
+
return out;
|
|
4699
|
+
}
|
|
4447
4700
|
function generateSHR(identityId, opts) {
|
|
4448
|
-
const { config, identityManager, masterKey, validityMs } = opts;
|
|
4701
|
+
const { config, identityManager, masterKey, validityMs, l4Evidence, now: nowOverride } = opts;
|
|
4449
4702
|
const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
|
|
4450
4703
|
if (!identity) {
|
|
4451
4704
|
return "No identity available for signing. Create an identity first.";
|
|
4452
4705
|
}
|
|
4453
|
-
const now = /* @__PURE__ */ new Date();
|
|
4706
|
+
const now = nowOverride ?? /* @__PURE__ */ new Date();
|
|
4454
4707
|
const expiresAt = new Date(now.getTime() + (validityMs ?? DEFAULT_VALIDITY_MS));
|
|
4455
4708
|
const degradations = [];
|
|
4456
4709
|
if (config.execution.environment === "local-process") {
|
|
@@ -4469,6 +4722,9 @@ function generateSHR(identityId, opts) {
|
|
|
4469
4722
|
mitigation: "TEE attestation planned for a future release"
|
|
4470
4723
|
});
|
|
4471
4724
|
}
|
|
4725
|
+
const l4Degradations = l4Evidence ? deriveL4Degradations(l4Evidence, now) : [];
|
|
4726
|
+
degradations.push(...l4Degradations);
|
|
4727
|
+
const l4Status = l4Degradations.length > 0 ? "degraded" : "active";
|
|
4472
4728
|
const body = {
|
|
4473
4729
|
shr_version: "1.0",
|
|
4474
4730
|
implementation: {
|
|
@@ -4499,7 +4755,7 @@ function generateSHR(identityId, opts) {
|
|
|
4499
4755
|
selective_disclosure: true
|
|
4500
4756
|
},
|
|
4501
4757
|
l4: {
|
|
4502
|
-
status:
|
|
4758
|
+
status: l4Status,
|
|
4503
4759
|
reputation_mode: config.reputation.mode,
|
|
4504
4760
|
attestation_format: config.reputation.attestation_format,
|
|
4505
4761
|
reputation_portable: true
|
|
@@ -7460,7 +7716,7 @@ function generateFortressViewHTML(options) {
|
|
|
7460
7716
|
<head>
|
|
7461
7717
|
<meta charset="UTF-8">
|
|
7462
7718
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7463
|
-
<title>Sanctuary
|
|
7719
|
+
<title>Sanctuary</title>
|
|
7464
7720
|
<style>
|
|
7465
7721
|
:root {
|
|
7466
7722
|
--bg: #0d1117;
|
|
@@ -7849,7 +8105,7 @@ function generateFortressViewHTML(options) {
|
|
|
7849
8105
|
<div class="fortress-brand">
|
|
7850
8106
|
<div class="shield">🛡</div>
|
|
7851
8107
|
<div>
|
|
7852
|
-
<h1>Sanctuary
|
|
8108
|
+
<h1>Sanctuary</h1>
|
|
7853
8109
|
<div class="version">v${esc(options.serverVersion)}</div>
|
|
7854
8110
|
</div>
|
|
7855
8111
|
</div>
|
|
@@ -8391,6 +8647,10 @@ var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
|
8391
8647
|
var RATE_LIMIT_GENERAL = 120;
|
|
8392
8648
|
var RATE_LIMIT_DECISIONS = 20;
|
|
8393
8649
|
var MAX_RATE_LIMIT_ENTRIES = 1e4;
|
|
8650
|
+
function isDashboardViewRoute(method, path) {
|
|
8651
|
+
if (method !== "GET") return false;
|
|
8652
|
+
return path === "/" || path === "/dashboard" || path === "/fortress" || path === "/events";
|
|
8653
|
+
}
|
|
8394
8654
|
var DashboardApprovalChannel = class {
|
|
8395
8655
|
config;
|
|
8396
8656
|
pending = /* @__PURE__ */ new Map();
|
|
@@ -8458,20 +8718,22 @@ var DashboardApprovalChannel = class {
|
|
|
8458
8718
|
* Start the HTTP(S) server for the dashboard.
|
|
8459
8719
|
*/
|
|
8460
8720
|
async start() {
|
|
8721
|
+
const handler = (req, res) => this.handleRequest(req, res);
|
|
8722
|
+
let server;
|
|
8723
|
+
if (this.useTLS && this.config.tls) {
|
|
8724
|
+
const tlsOpts = {
|
|
8725
|
+
cert: await promises.readFile(this.config.tls.cert_path),
|
|
8726
|
+
key: await promises.readFile(this.config.tls.key_path)
|
|
8727
|
+
};
|
|
8728
|
+
server = https.createServer(tlsOpts, handler);
|
|
8729
|
+
} else {
|
|
8730
|
+
server = http.createServer(handler);
|
|
8731
|
+
}
|
|
8732
|
+
this.httpServer = server;
|
|
8461
8733
|
return new Promise((resolve, reject) => {
|
|
8462
|
-
const handler = (req, res) => this.handleRequest(req, res);
|
|
8463
|
-
if (this.useTLS && this.config.tls) {
|
|
8464
|
-
const tlsOpts = {
|
|
8465
|
-
cert: fs.readFileSync(this.config.tls.cert_path),
|
|
8466
|
-
key: fs.readFileSync(this.config.tls.key_path)
|
|
8467
|
-
};
|
|
8468
|
-
this.httpServer = https.createServer(tlsOpts, handler);
|
|
8469
|
-
} else {
|
|
8470
|
-
this.httpServer = http.createServer(handler);
|
|
8471
|
-
}
|
|
8472
8734
|
const protocol = this.useTLS ? "https" : "http";
|
|
8473
8735
|
const baseUrl = `${protocol}://${this.config.host}:${this.config.port}`;
|
|
8474
|
-
|
|
8736
|
+
server.listen(this.config.port, this.config.host, () => {
|
|
8475
8737
|
const sessionUrl = this.authToken ? this.createSessionUrl() : baseUrl;
|
|
8476
8738
|
process.stderr.write(
|
|
8477
8739
|
`
|
|
@@ -8495,7 +8757,7 @@ var DashboardApprovalChannel = class {
|
|
|
8495
8757
|
}
|
|
8496
8758
|
resolve();
|
|
8497
8759
|
});
|
|
8498
|
-
|
|
8760
|
+
server.on("error", (err) => {
|
|
8499
8761
|
if (err.code === "EADDRINUSE") {
|
|
8500
8762
|
const port = this.config.port;
|
|
8501
8763
|
process.stderr.write(
|
|
@@ -8784,13 +9046,14 @@ var DashboardApprovalChannel = class {
|
|
|
8784
9046
|
}
|
|
8785
9047
|
if (method === "GET" && url.pathname === "/" && this.authToken) {
|
|
8786
9048
|
if (!this.isAuthenticated(req, url)) {
|
|
8787
|
-
if (!this.checkRateLimit(req, res, "general")) return;
|
|
8788
9049
|
this.serveLoginPage(res);
|
|
8789
9050
|
return;
|
|
8790
9051
|
}
|
|
8791
9052
|
}
|
|
8792
9053
|
if (!this.checkAuth(req, url, res)) return;
|
|
8793
|
-
if (!
|
|
9054
|
+
if (!isDashboardViewRoute(method, url.pathname)) {
|
|
9055
|
+
if (!this.checkRateLimit(req, res, "general")) return;
|
|
9056
|
+
}
|
|
8794
9057
|
try {
|
|
8795
9058
|
if (method === "GET" && url.pathname === "/fortress") {
|
|
8796
9059
|
this.serveFortressView(res);
|
|
@@ -9084,16 +9347,7 @@ data: ${JSON.stringify(initData)}
|
|
|
9084
9347
|
res.end(JSON.stringify({ identities: [], count: 0 }));
|
|
9085
9348
|
return;
|
|
9086
9349
|
}
|
|
9087
|
-
const identities = this.identityManager.
|
|
9088
|
-
identity_id: id.identity_id,
|
|
9089
|
-
label: id.label,
|
|
9090
|
-
public_key: id.public_key,
|
|
9091
|
-
did: id.did,
|
|
9092
|
-
created_at: id.created_at,
|
|
9093
|
-
key_type: id.key_type,
|
|
9094
|
-
key_protection: id.key_protection,
|
|
9095
|
-
rotation_count: id.rotation_history?.length ?? 0
|
|
9096
|
-
}));
|
|
9350
|
+
const identities = this.identityManager.listWithRotationCount();
|
|
9097
9351
|
const primary = this.identityManager.getDefault();
|
|
9098
9352
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
9099
9353
|
res.end(JSON.stringify({
|
|
@@ -9698,16 +9952,6 @@ var INVISIBLE_CHARS = [
|
|
|
9698
9952
|
];
|
|
9699
9953
|
var VARIATION_SELECTOR_RANGE_START = 65024;
|
|
9700
9954
|
var VARIATION_SELECTOR_RANGE_END = 65039;
|
|
9701
|
-
var ZERO_WIDTH_CHARS = [
|
|
9702
|
-
"\u200B",
|
|
9703
|
-
// Zero-width space
|
|
9704
|
-
"\u200C",
|
|
9705
|
-
// Zero-width non-joiner
|
|
9706
|
-
"\u200D",
|
|
9707
|
-
// Zero-width joiner
|
|
9708
|
-
"\uFEFF"
|
|
9709
|
-
// Zero-width no-break space
|
|
9710
|
-
];
|
|
9711
9955
|
var BASE64_STANDARD_PATTERN = /^[A-Za-z0-9+/]+={0,2}$/;
|
|
9712
9956
|
var BASE64URL_PATTERN = /^[A-Za-z0-9_-]+={0,2}$/;
|
|
9713
9957
|
var BASE64_BLOCK_PATTERN = /[A-Za-z0-9+/]{20,}={0,2}/g;
|
|
@@ -10324,10 +10568,8 @@ var InjectionDetector = class {
|
|
|
10324
10568
|
});
|
|
10325
10569
|
}
|
|
10326
10570
|
}
|
|
10327
|
-
|
|
10328
|
-
|
|
10329
|
-
zeroWidthCount += (value.match(new RegExp(char, "g")) || []).length;
|
|
10330
|
-
}
|
|
10571
|
+
const zeroWidthMatches = value.match(/[\u200B\u200C\u200D\uFEFF]/g);
|
|
10572
|
+
const zeroWidthCount = zeroWidthMatches ? zeroWidthMatches.length : 0;
|
|
10331
10573
|
if (zeroWidthCount > 0) {
|
|
10332
10574
|
signals.push({
|
|
10333
10575
|
type: "encoding_evasion",
|
|
@@ -11320,6 +11562,16 @@ function transformDegradations(degradations) {
|
|
|
11320
11562
|
authzImpact = "Must share entire data context, cannot redact";
|
|
11321
11563
|
} else if (deg.code === "BASIC_SYBIL_ONLY") {
|
|
11322
11564
|
authzImpact = "Restrict to interactions with known agents only";
|
|
11565
|
+
} else if (deg.code === "NO_REPUTATION_HISTORY") {
|
|
11566
|
+
authzImpact = "No prior reputation \u2014 treat as a new counterparty; require escrow or human approval for value transfer";
|
|
11567
|
+
} else if (deg.code === "LOW_TIER_DOMINANCE") {
|
|
11568
|
+
authzImpact = "Reputation dominated by self-attested / unverified signers \u2014 weight accordingly, require additional confirmation on high-value actions";
|
|
11569
|
+
} else if (deg.code === "STALE_REPUTATION") {
|
|
11570
|
+
authzImpact = "Reputation is stale \u2014 may not reflect current behavior; refresh before relying on it";
|
|
11571
|
+
} else if (deg.code === "DISPUTE_ON_RECORD") {
|
|
11572
|
+
authzImpact = "Disputes on record \u2014 review dispute context before extending trust beyond low-value interactions";
|
|
11573
|
+
} else if (deg.code === "NO_VERASCORE_LINK") {
|
|
11574
|
+
authzImpact = "No external reputation profile \u2014 counterparty cannot independently discover reputation bundle";
|
|
11323
11575
|
} else {
|
|
11324
11576
|
authzImpact = "Unknown authorization impact";
|
|
11325
11577
|
}
|
|
@@ -11426,12 +11678,37 @@ function transformSHRGeneric(shr) {
|
|
|
11426
11678
|
}
|
|
11427
11679
|
|
|
11428
11680
|
// src/shr/tools.ts
|
|
11429
|
-
function
|
|
11681
|
+
async function gatherL4Evidence(reputationStore, auditLog, identity) {
|
|
11682
|
+
const summary = await reputationStore.summarizeForSHR(identity.did);
|
|
11683
|
+
const published = await auditLog.query({
|
|
11684
|
+
layer: "l4",
|
|
11685
|
+
operation_type: "reputation_publish",
|
|
11686
|
+
limit: 500
|
|
11687
|
+
});
|
|
11688
|
+
const verascoreLinked = published.entries.some(
|
|
11689
|
+
(e) => e.result === "success" && e.identity_id === identity.identity_id
|
|
11690
|
+
);
|
|
11691
|
+
return {
|
|
11692
|
+
attestation_count: summary.attestation_count,
|
|
11693
|
+
tier_distribution: summary.tier_distribution,
|
|
11694
|
+
most_recent_attestation_at: summary.most_recent_attestation_at,
|
|
11695
|
+
dispute_count: summary.dispute_count,
|
|
11696
|
+
context_breakdown: summary.context_breakdown,
|
|
11697
|
+
verascore_linked: verascoreLinked
|
|
11698
|
+
};
|
|
11699
|
+
}
|
|
11700
|
+
function createSHRTools(config, identityManager, masterKey, auditLog, reputationStore) {
|
|
11430
11701
|
const generatorOpts = {
|
|
11431
11702
|
config,
|
|
11432
11703
|
identityManager,
|
|
11433
11704
|
masterKey
|
|
11434
11705
|
};
|
|
11706
|
+
async function resolveL4Evidence(identityId) {
|
|
11707
|
+
if (!reputationStore) return void 0;
|
|
11708
|
+
const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
|
|
11709
|
+
if (!identity) return void 0;
|
|
11710
|
+
return gatherL4Evidence(reputationStore, auditLog, identity);
|
|
11711
|
+
}
|
|
11435
11712
|
const tools = [
|
|
11436
11713
|
{
|
|
11437
11714
|
name: "shr_generate",
|
|
@@ -11451,9 +11728,12 @@ function createSHRTools(config, identityManager, masterKey, auditLog) {
|
|
|
11451
11728
|
},
|
|
11452
11729
|
handler: async (args) => {
|
|
11453
11730
|
const validityMs = args.validity_minutes ? args.validity_minutes * 60 * 1e3 : void 0;
|
|
11454
|
-
const
|
|
11731
|
+
const identityId = args.identity_id;
|
|
11732
|
+
const l4Evidence = await resolveL4Evidence(identityId);
|
|
11733
|
+
const result = generateSHR(identityId, {
|
|
11455
11734
|
...generatorOpts,
|
|
11456
|
-
validityMs
|
|
11735
|
+
validityMs,
|
|
11736
|
+
l4Evidence
|
|
11457
11737
|
});
|
|
11458
11738
|
if (typeof result === "string") {
|
|
11459
11739
|
return toolResult({ error: result });
|
|
@@ -11512,9 +11792,12 @@ function createSHRTools(config, identityManager, masterKey, auditLog) {
|
|
|
11512
11792
|
handler: async (args) => {
|
|
11513
11793
|
const format = args.format || "ping";
|
|
11514
11794
|
const validityMs = args.validity_minutes ? args.validity_minutes * 60 * 1e3 : void 0;
|
|
11515
|
-
const
|
|
11795
|
+
const identityId = args.identity_id;
|
|
11796
|
+
const l4Evidence = await resolveL4Evidence(identityId);
|
|
11797
|
+
const shrResult = generateSHR(identityId, {
|
|
11516
11798
|
...generatorOpts,
|
|
11517
|
-
validityMs
|
|
11799
|
+
validityMs,
|
|
11800
|
+
l4Evidence
|
|
11518
11801
|
});
|
|
11519
11802
|
if (typeof shrResult === "string") {
|
|
11520
11803
|
return toolResult({ error: shrResult });
|
|
@@ -17213,8 +17496,27 @@ function validateVerascoreUrl(urlStr, configuredUrl) {
|
|
|
17213
17496
|
}
|
|
17214
17497
|
}
|
|
17215
17498
|
function createSanctuaryTools(opts) {
|
|
17216
|
-
const { config, identityManager, masterKey, auditLog, policy, keyProtection } = opts;
|
|
17499
|
+
const { config, identityManager, masterKey, auditLog, policy, keyProtection, reputationStore } = opts;
|
|
17217
17500
|
const identityEncKey = derivePurposeKey(masterKey, "identity-encryption");
|
|
17501
|
+
async function l4EvidenceForIdentity(identity) {
|
|
17502
|
+
if (!reputationStore) return void 0;
|
|
17503
|
+
return gatherL4Evidence(reputationStore, auditLog, identity);
|
|
17504
|
+
}
|
|
17505
|
+
function emptyL4Evidence() {
|
|
17506
|
+
return {
|
|
17507
|
+
attestation_count: 0,
|
|
17508
|
+
tier_distribution: {
|
|
17509
|
+
"verified-sovereign": 0,
|
|
17510
|
+
"verified-degraded": 0,
|
|
17511
|
+
"self-attested": 0,
|
|
17512
|
+
"unverified": 0
|
|
17513
|
+
},
|
|
17514
|
+
most_recent_attestation_at: null,
|
|
17515
|
+
dispute_count: 0,
|
|
17516
|
+
context_breakdown: {},
|
|
17517
|
+
verascore_linked: false
|
|
17518
|
+
};
|
|
17519
|
+
}
|
|
17218
17520
|
const tools = [
|
|
17219
17521
|
// ─── sanctuary_bootstrap ───────────────────────────────────────────
|
|
17220
17522
|
{
|
|
@@ -17254,7 +17556,8 @@ function createSanctuaryTools(opts) {
|
|
|
17254
17556
|
const shr = generateSHR(publicIdentity.identity_id, {
|
|
17255
17557
|
config,
|
|
17256
17558
|
identityManager,
|
|
17257
|
-
masterKey
|
|
17559
|
+
masterKey,
|
|
17560
|
+
l4Evidence: emptyL4Evidence()
|
|
17258
17561
|
});
|
|
17259
17562
|
if (typeof shr === "string") {
|
|
17260
17563
|
return toolResult({
|
|
@@ -17413,10 +17716,12 @@ function createSanctuaryTools(opts) {
|
|
|
17413
17716
|
error: "No identity found. Create one with identity_create first."
|
|
17414
17717
|
});
|
|
17415
17718
|
}
|
|
17719
|
+
const l4Evidence = await l4EvidenceForIdentity(identity);
|
|
17416
17720
|
const shr = generateSHR(identity.identity_id, {
|
|
17417
17721
|
config,
|
|
17418
17722
|
identityManager,
|
|
17419
|
-
masterKey
|
|
17723
|
+
masterKey,
|
|
17724
|
+
l4Evidence
|
|
17420
17725
|
});
|
|
17421
17726
|
const attestations = args.attestations ?? [];
|
|
17422
17727
|
const body = {
|
|
@@ -19218,7 +19523,7 @@ the Sanctuary oversight gate recorded the following activity:
|
|
|
19218
19523
|
| Outcome | Count |
|
|
19219
19524
|
|---|---|
|
|
19220
19525
|
| Gate allow (Tier 3 auto-allow or approved Tier 1/2) | {{ gate_allow_count }} |
|
|
19221
|
-
| Gate allow_proxy (
|
|
19526
|
+
| Gate allow_proxy (Sanctuary MCP-proxy pass-through) | {{ gate_allow_proxy_count }} |
|
|
19222
19527
|
| Gate deny (approval denied or timeout) | {{ gate_deny_count }} |
|
|
19223
19528
|
| Gate escalate (Tier 2 anomaly raised for human review) | {{ gate_escalate_count }} |
|
|
19224
19529
|
| Gate unclassified (no matching rule, default behaviour applied) | {{ gate_unclassified_count }} |
|
|
@@ -20851,10 +21156,1206 @@ var MemoryStorage = class {
|
|
|
20851
21156
|
}
|
|
20852
21157
|
};
|
|
20853
21158
|
|
|
21159
|
+
// src/dashboard/aggregator.ts
|
|
21160
|
+
var L4_DEGRADATION_IMPACT = {
|
|
21161
|
+
critical: 40,
|
|
21162
|
+
warning: 25,
|
|
21163
|
+
info: 10
|
|
21164
|
+
};
|
|
21165
|
+
function computeL4LayerScore(degradations, status) {
|
|
21166
|
+
if (status === "compromised") return 0;
|
|
21167
|
+
let score = 100;
|
|
21168
|
+
for (const deg of degradations) {
|
|
21169
|
+
score -= L4_DEGRADATION_IMPACT[deg.severity] ?? 10;
|
|
21170
|
+
}
|
|
21171
|
+
score = Math.max(0, score);
|
|
21172
|
+
if (degradations.length === 0 && score > 50) {
|
|
21173
|
+
score = Math.min(100, score + 5);
|
|
21174
|
+
}
|
|
21175
|
+
return Math.round(score);
|
|
21176
|
+
}
|
|
21177
|
+
var MAX_ACTIVITY = 50;
|
|
21178
|
+
var MAX_AUDIT = 50;
|
|
21179
|
+
function fingerprintDID(did) {
|
|
21180
|
+
const raw = did.replace(/^did:[a-z0-9]+:/i, "");
|
|
21181
|
+
if (raw.length <= 12) return raw;
|
|
21182
|
+
return `${raw.slice(0, 6)}\u2026${raw.slice(-6)}`;
|
|
21183
|
+
}
|
|
21184
|
+
function countInjectionsToday(audit) {
|
|
21185
|
+
const startOfDay = /* @__PURE__ */ new Date();
|
|
21186
|
+
startOfDay.setHours(0, 0, 0, 0);
|
|
21187
|
+
const cutoff = startOfDay.getTime();
|
|
21188
|
+
return audit.filter((e) => {
|
|
21189
|
+
const ts = new Date(e.timestamp).getTime();
|
|
21190
|
+
if (isNaN(ts) || ts < cutoff) return false;
|
|
21191
|
+
const op = (e.operation ?? "").toLowerCase();
|
|
21192
|
+
return op.includes("injection") || op.includes("blocked");
|
|
21193
|
+
}).length;
|
|
21194
|
+
}
|
|
21195
|
+
var PROOF_CREATION_OPS = /* @__PURE__ */ new Set([
|
|
21196
|
+
"zk_prove",
|
|
21197
|
+
"zk_range_prove",
|
|
21198
|
+
"proof_commitment"
|
|
21199
|
+
]);
|
|
21200
|
+
function countProofsToday(audit) {
|
|
21201
|
+
const startOfDay = /* @__PURE__ */ new Date();
|
|
21202
|
+
startOfDay.setHours(0, 0, 0, 0);
|
|
21203
|
+
const cutoff = startOfDay.getTime();
|
|
21204
|
+
return audit.filter((e) => {
|
|
21205
|
+
if (e.layer !== "l3") return false;
|
|
21206
|
+
if (!PROOF_CREATION_OPS.has(e.operation)) return false;
|
|
21207
|
+
const ts = new Date(e.timestamp).getTime();
|
|
21208
|
+
return !isNaN(ts) && ts >= cutoff;
|
|
21209
|
+
}).length;
|
|
21210
|
+
}
|
|
21211
|
+
function buildAgent(sources) {
|
|
21212
|
+
if (!sources.identityManager) {
|
|
21213
|
+
return {
|
|
21214
|
+
display_name: "Unclaimed agent",
|
|
21215
|
+
did: null,
|
|
21216
|
+
did_fingerprint: null,
|
|
21217
|
+
identity_count: 0,
|
|
21218
|
+
primary_identity_id: null
|
|
21219
|
+
};
|
|
21220
|
+
}
|
|
21221
|
+
const primary = sources.identityManager.getDefault();
|
|
21222
|
+
const identities = sources.identityManager.list();
|
|
21223
|
+
if (!primary) {
|
|
21224
|
+
return {
|
|
21225
|
+
display_name: "Unclaimed agent",
|
|
21226
|
+
did: null,
|
|
21227
|
+
did_fingerprint: null,
|
|
21228
|
+
identity_count: identities.length,
|
|
21229
|
+
primary_identity_id: null
|
|
21230
|
+
};
|
|
21231
|
+
}
|
|
21232
|
+
return {
|
|
21233
|
+
display_name: primary.label || "Sovereign agent",
|
|
21234
|
+
did: primary.did,
|
|
21235
|
+
did_fingerprint: fingerprintDID(primary.did),
|
|
21236
|
+
identity_count: identities.length,
|
|
21237
|
+
primary_identity_id: primary.identity_id
|
|
21238
|
+
};
|
|
21239
|
+
}
|
|
21240
|
+
function buildL1(sources, audit) {
|
|
21241
|
+
const hasIdentity = !!sources.identityManager?.getDefault();
|
|
21242
|
+
const state = hasIdentity ? "full" : "degraded";
|
|
21243
|
+
return {
|
|
21244
|
+
label: "L1 Cognitive",
|
|
21245
|
+
state,
|
|
21246
|
+
headline: hasIdentity ? "State encrypted at rest" : "No sovereign identity \u2014 run sanctuary_bootstrap",
|
|
21247
|
+
encryption: "AES-256-GCM + HKDF per namespace",
|
|
21248
|
+
injection_blocked_today: countInjectionsToday(audit),
|
|
21249
|
+
memory_attest_ready: hasIdentity
|
|
21250
|
+
};
|
|
21251
|
+
}
|
|
21252
|
+
function buildL2(sources) {
|
|
21253
|
+
const teeAvailable = sources.teeAvailable ?? false;
|
|
21254
|
+
const state = teeAvailable ? "full" : "degraded";
|
|
21255
|
+
return {
|
|
21256
|
+
label: "L2 Operational",
|
|
21257
|
+
state,
|
|
21258
|
+
headline: teeAvailable ? "Hardware isolation active" : "Process isolation \u2014 no TEE on this host",
|
|
21259
|
+
isolation_type: teeAvailable ? "hardware-tee" : "process-level",
|
|
21260
|
+
tee_available: teeAvailable,
|
|
21261
|
+
tee_status: teeAvailable ? "Attested" : "Not available \u2014 normal on local dev",
|
|
21262
|
+
sandbox_status: "Principal Policy gate active"
|
|
21263
|
+
};
|
|
21264
|
+
}
|
|
21265
|
+
function buildL3(sources, audit) {
|
|
21266
|
+
const VC_ISSUING_OPS = /* @__PURE__ */ new Set([
|
|
21267
|
+
"reputation_record",
|
|
21268
|
+
"bootstrap_provide_guarantee",
|
|
21269
|
+
"reputation_publish"
|
|
21270
|
+
]);
|
|
21271
|
+
const didActive = !!sources.identityManager?.getDefault()?.did;
|
|
21272
|
+
const vcCount = audit.filter(
|
|
21273
|
+
(e) => e.layer === "l4" && VC_ISSUING_OPS.has(e.operation)
|
|
21274
|
+
).length;
|
|
21275
|
+
return {
|
|
21276
|
+
label: "L3 Disclosure",
|
|
21277
|
+
state: didActive ? "full" : "degraded",
|
|
21278
|
+
headline: didActive ? "Selective disclosure ready" : "No DID \u2014 disclosure unavailable",
|
|
21279
|
+
did_active: didActive,
|
|
21280
|
+
vc_count: vcCount,
|
|
21281
|
+
proofs_today: countProofsToday(audit)
|
|
21282
|
+
};
|
|
21283
|
+
}
|
|
21284
|
+
function buildL4(sources) {
|
|
21285
|
+
const rep = sources.reputation;
|
|
21286
|
+
const hasDid = !!sources.identityManager?.getDefault()?.did;
|
|
21287
|
+
const evidenceBlock = buildL4EvidenceBlock(sources);
|
|
21288
|
+
const base = rep?.score != null ? {
|
|
21289
|
+
label: "L4 Reputation",
|
|
21290
|
+
state: "full",
|
|
21291
|
+
headline: "Verascore attached",
|
|
21292
|
+
score: rep.score,
|
|
21293
|
+
profile_url: rep.profile_url,
|
|
21294
|
+
claim_cta: null
|
|
21295
|
+
} : hasDid ? {
|
|
21296
|
+
label: "L4 Reputation",
|
|
21297
|
+
state: "degraded",
|
|
21298
|
+
headline: "Claim your profile",
|
|
21299
|
+
score: null,
|
|
21300
|
+
profile_url: null,
|
|
21301
|
+
claim_cta: "Claim your profile at verascore.ai"
|
|
21302
|
+
} : {
|
|
21303
|
+
label: "L4 Reputation",
|
|
21304
|
+
state: "degraded",
|
|
21305
|
+
headline: "No identity claimed",
|
|
21306
|
+
score: null,
|
|
21307
|
+
profile_url: null,
|
|
21308
|
+
claim_cta: "Claim your profile at verascore.ai"
|
|
21309
|
+
};
|
|
21310
|
+
if (!evidenceBlock) return base;
|
|
21311
|
+
const nextState = evidenceBlock.active_degradations.length > 0 && base.state === "full" ? "degraded" : base.state;
|
|
21312
|
+
const nextHeadline = nextState === base.state ? base.headline : "Attached, but evidence is degraded";
|
|
21313
|
+
return {
|
|
21314
|
+
...base,
|
|
21315
|
+
state: nextState,
|
|
21316
|
+
headline: nextHeadline,
|
|
21317
|
+
evidence: evidenceBlock.evidence,
|
|
21318
|
+
layer_score: evidenceBlock.layer_score,
|
|
21319
|
+
active_degradations: evidenceBlock.active_degradations
|
|
21320
|
+
};
|
|
21321
|
+
}
|
|
21322
|
+
function buildL4EvidenceBlock(sources) {
|
|
21323
|
+
const ev = sources.l4Evidence;
|
|
21324
|
+
if (!ev) return null;
|
|
21325
|
+
const degradations = deriveL4Degradations(ev, sources.l4Now ?? /* @__PURE__ */ new Date());
|
|
21326
|
+
const status = degradations.length > 0 ? "degraded" : "full";
|
|
21327
|
+
const layer_score = computeL4LayerScore(degradations, status);
|
|
21328
|
+
return {
|
|
21329
|
+
evidence: {
|
|
21330
|
+
attestation_count: ev.attestation_count,
|
|
21331
|
+
tier_distribution: ev.tier_distribution,
|
|
21332
|
+
most_recent_attestation_at: ev.most_recent_attestation_at,
|
|
21333
|
+
dispute_count: ev.dispute_count,
|
|
21334
|
+
context_breakdown: ev.context_breakdown ?? {},
|
|
21335
|
+
verascore_linked: ev.verascore_linked
|
|
21336
|
+
},
|
|
21337
|
+
layer_score,
|
|
21338
|
+
active_degradations: degradations.map((d) => ({
|
|
21339
|
+
code: d.code,
|
|
21340
|
+
severity: d.severity,
|
|
21341
|
+
description: d.description,
|
|
21342
|
+
...d.mitigation !== void 0 ? { mitigation: d.mitigation } : {}
|
|
21343
|
+
}))
|
|
21344
|
+
};
|
|
21345
|
+
}
|
|
21346
|
+
function computeOverall(l1, l2, l3, l4) {
|
|
21347
|
+
const critical = [l1.state, l3.state, l4.state];
|
|
21348
|
+
if (critical.includes("compromised") || l2.state === "compromised") {
|
|
21349
|
+
return {
|
|
21350
|
+
status: "compromised",
|
|
21351
|
+
light: "red",
|
|
21352
|
+
headline: "Sovereignty compromised"
|
|
21353
|
+
};
|
|
21354
|
+
}
|
|
21355
|
+
const allCriticalFull = critical.every((s) => s === "full");
|
|
21356
|
+
if (allCriticalFull && l2.state === "full") {
|
|
21357
|
+
return {
|
|
21358
|
+
status: "healthy",
|
|
21359
|
+
light: "green",
|
|
21360
|
+
headline: "All layers full"
|
|
21361
|
+
};
|
|
21362
|
+
}
|
|
21363
|
+
if (allCriticalFull && l2.state === "degraded") {
|
|
21364
|
+
return {
|
|
21365
|
+
status: "healthy",
|
|
21366
|
+
light: "green",
|
|
21367
|
+
headline: "L1\xB7L3\xB7L4 full \u2014 L2 degraded (no TEE on this host)"
|
|
21368
|
+
};
|
|
21369
|
+
}
|
|
21370
|
+
return {
|
|
21371
|
+
status: "degraded",
|
|
21372
|
+
light: "yellow",
|
|
21373
|
+
headline: "One or more layers degraded"
|
|
21374
|
+
};
|
|
21375
|
+
}
|
|
21376
|
+
function buildUpstreamServers(sources) {
|
|
21377
|
+
if (!sources.clientManager) return [];
|
|
21378
|
+
return sources.clientManager.getStatus().map((s) => {
|
|
21379
|
+
const entry = {
|
|
21380
|
+
name: s.name,
|
|
21381
|
+
state: s.state,
|
|
21382
|
+
tool_count: s.tool_count
|
|
21383
|
+
};
|
|
21384
|
+
if (s.error) entry.error = s.error;
|
|
21385
|
+
return entry;
|
|
21386
|
+
});
|
|
21387
|
+
}
|
|
21388
|
+
async function getProtectionSnapshot(sources) {
|
|
21389
|
+
let audit = [];
|
|
21390
|
+
if (sources.auditLog) {
|
|
21391
|
+
try {
|
|
21392
|
+
const result = await sources.auditLog.query({ limit: MAX_AUDIT });
|
|
21393
|
+
audit = result.entries;
|
|
21394
|
+
} catch {
|
|
21395
|
+
audit = [];
|
|
21396
|
+
}
|
|
21397
|
+
}
|
|
21398
|
+
const agent = buildAgent(sources);
|
|
21399
|
+
const l1 = buildL1(sources, audit);
|
|
21400
|
+
const l2 = buildL2(sources);
|
|
21401
|
+
const l3 = buildL3(sources, audit);
|
|
21402
|
+
const l4 = buildL4(sources);
|
|
21403
|
+
const activity = (sources.activity ?? []).slice(0, MAX_ACTIVITY);
|
|
21404
|
+
const pending_approvals = sources.pendingApprovals ?? [];
|
|
21405
|
+
const upstream_servers = buildUpstreamServers(sources);
|
|
21406
|
+
return {
|
|
21407
|
+
overall: computeOverall(l1, l2, l3, l4),
|
|
21408
|
+
agent,
|
|
21409
|
+
layers: { l1, l2, l3, l4 },
|
|
21410
|
+
activity,
|
|
21411
|
+
pending_approvals,
|
|
21412
|
+
audit: audit.slice(-MAX_AUDIT).reverse(),
|
|
21413
|
+
upstream_servers,
|
|
21414
|
+
mode: sources.mode,
|
|
21415
|
+
server_version: sources.server_version,
|
|
21416
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
21417
|
+
};
|
|
21418
|
+
}
|
|
21419
|
+
|
|
21420
|
+
// src/dashboard/html.ts
|
|
21421
|
+
var HERO_COPY = "Your agent is protected.";
|
|
21422
|
+
function escHtml(value) {
|
|
21423
|
+
if (value == null) return "";
|
|
21424
|
+
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
21425
|
+
}
|
|
21426
|
+
function layerCard(layer, extra) {
|
|
21427
|
+
return `<section class="layer-card layer-${escHtml(layer.state)}" data-layer-label="${escHtml(layer.label)}">
|
|
21428
|
+
<div class="layer-head">
|
|
21429
|
+
<div class="layer-dot"></div>
|
|
21430
|
+
<div>
|
|
21431
|
+
<h3>${escHtml(layer.label)}</h3>
|
|
21432
|
+
<p class="layer-headline">${escHtml(layer.headline)}</p>
|
|
21433
|
+
</div>
|
|
21434
|
+
</div>
|
|
21435
|
+
<dl class="layer-detail">${extra}</dl>
|
|
21436
|
+
</section>`;
|
|
21437
|
+
}
|
|
21438
|
+
function l1Card(l1) {
|
|
21439
|
+
return layerCard(
|
|
21440
|
+
l1,
|
|
21441
|
+
`<div><dt>Encryption</dt><dd>${escHtml(l1.encryption)}</dd></div>
|
|
21442
|
+
<div><dt>Injections blocked today</dt><dd>${escHtml(l1.injection_blocked_today)}</dd></div>
|
|
21443
|
+
<div><dt>memory_attest</dt><dd>${l1.memory_attest_ready ? "Ready" : "Not ready"}</dd></div>`
|
|
21444
|
+
);
|
|
21445
|
+
}
|
|
21446
|
+
function l2Card(l2) {
|
|
21447
|
+
return layerCard(
|
|
21448
|
+
l2,
|
|
21449
|
+
`<div><dt>Isolation</dt><dd>${escHtml(l2.isolation_type)}</dd></div>
|
|
21450
|
+
<div><dt>TEE</dt><dd>${escHtml(l2.tee_status)}</dd></div>
|
|
21451
|
+
<div><dt>Sandbox</dt><dd>${escHtml(l2.sandbox_status)}</dd></div>`
|
|
21452
|
+
);
|
|
21453
|
+
}
|
|
21454
|
+
function l3Card(l3) {
|
|
21455
|
+
return layerCard(
|
|
21456
|
+
l3,
|
|
21457
|
+
`<div><dt>DID</dt><dd>${l3.did_active ? "Active" : "None"}</dd></div>
|
|
21458
|
+
<div><dt>Credentials</dt><dd>${escHtml(l3.vc_count)}</dd></div>
|
|
21459
|
+
<div><dt>Proofs today</dt><dd>${escHtml(l3.proofs_today)}</dd></div>`
|
|
21460
|
+
);
|
|
21461
|
+
}
|
|
21462
|
+
function l4Card(l4) {
|
|
21463
|
+
const score = l4.score != null ? `<div class="score-block"><span class="score-value">${escHtml(l4.score)}</span><span class="score-label">Verascore</span></div>` : `<div class="claim-block">${escHtml(l4.claim_cta ?? "Claim your profile at verascore.ai")}</div>`;
|
|
21464
|
+
return layerCard(
|
|
21465
|
+
l4,
|
|
21466
|
+
`<div class="layer-cta">${score}</div>${l4EvidenceBlock(l4)}`
|
|
21467
|
+
);
|
|
21468
|
+
}
|
|
21469
|
+
function formatRelativeDays(iso) {
|
|
21470
|
+
if (!iso) return "none on record";
|
|
21471
|
+
const ts = new Date(iso).getTime();
|
|
21472
|
+
if (isNaN(ts)) return "unknown";
|
|
21473
|
+
const days = Math.round((Date.now() - ts) / (24 * 60 * 60 * 1e3));
|
|
21474
|
+
if (days <= 0) return "today";
|
|
21475
|
+
if (days === 1) return "1 day ago";
|
|
21476
|
+
return `${days} days ago`;
|
|
21477
|
+
}
|
|
21478
|
+
function l4EvidenceBlock(l4) {
|
|
21479
|
+
if (!l4.evidence) return "";
|
|
21480
|
+
const ev = l4.evidence;
|
|
21481
|
+
const tierSovereign = ev.tier_distribution["verified-sovereign"];
|
|
21482
|
+
const tierDegraded = ev.tier_distribution["verified-degraded"];
|
|
21483
|
+
const tierSelf = ev.tier_distribution["self-attested"];
|
|
21484
|
+
const tierUnverified = ev.tier_distribution["unverified"];
|
|
21485
|
+
const contextCount = Object.keys(ev.context_breakdown).length;
|
|
21486
|
+
const mostRecent = formatRelativeDays(ev.most_recent_attestation_at);
|
|
21487
|
+
const score = l4.layer_score ?? 100;
|
|
21488
|
+
const summaryLine = `
|
|
21489
|
+
<div><dt>L4 score</dt><dd>${escHtml(score)} / 100</dd></div>
|
|
21490
|
+
<div><dt>Attestations</dt><dd>${escHtml(ev.attestation_count)}</dd></div>
|
|
21491
|
+
<div><dt>Verified tiers</dt><dd>${escHtml(tierSovereign)} sovereign \xB7 ${escHtml(tierDegraded)} degraded</dd></div>
|
|
21492
|
+
<div><dt>Lower tiers</dt><dd>${escHtml(tierSelf)} self \xB7 ${escHtml(tierUnverified)} unverified</dd></div>
|
|
21493
|
+
<div><dt>Contexts</dt><dd>${escHtml(contextCount)}</dd></div>
|
|
21494
|
+
<div><dt>Disputes</dt><dd>${escHtml(ev.dispute_count)}</dd></div>
|
|
21495
|
+
<div><dt>Last activity</dt><dd>${escHtml(mostRecent)}</dd></div>
|
|
21496
|
+
<div><dt>Verascore link</dt><dd>${ev.verascore_linked ? "Yes" : "Not linked"}</dd></div>
|
|
21497
|
+
`;
|
|
21498
|
+
const degs = l4.active_degradations ?? [];
|
|
21499
|
+
const degList = degs.length === 0 ? "" : `<ul class="l4-deg-list">${degs.map(
|
|
21500
|
+
(d) => `
|
|
21501
|
+
<li class="l4-deg l4-deg-${escHtml(d.severity)}">
|
|
21502
|
+
<div class="l4-deg-head">
|
|
21503
|
+
<span class="l4-deg-code">${escHtml(d.code)}</span>
|
|
21504
|
+
<span class="l4-deg-sev">${escHtml(d.severity)}</span>
|
|
21505
|
+
</div>
|
|
21506
|
+
<p class="l4-deg-desc">${escHtml(d.description)}</p>
|
|
21507
|
+
${d.mitigation ? `<p class="l4-deg-mit">${escHtml(d.mitigation)}</p>` : ""}
|
|
21508
|
+
</li>`
|
|
21509
|
+
).join("")}</ul>`;
|
|
21510
|
+
return `
|
|
21511
|
+
<div class="l4-evidence">
|
|
21512
|
+
<dl class="layer-detail l4-evidence-summary">${summaryLine}</dl>
|
|
21513
|
+
${degList}
|
|
21514
|
+
</div>
|
|
21515
|
+
`;
|
|
21516
|
+
}
|
|
21517
|
+
function renderDashboardHTML(options) {
|
|
21518
|
+
const { snapshot } = options;
|
|
21519
|
+
const { overall, agent, layers, activity, pending_approvals, audit, upstream_servers } = snapshot;
|
|
21520
|
+
const activityRows = activity.length === 0 ? `<tr class="empty"><td colspan="5">Waiting for tool calls\u2026</td></tr>` : activity.map((entry) => {
|
|
21521
|
+
const time = new Date(entry.timestamp).toLocaleTimeString();
|
|
21522
|
+
return `<tr class="result-${escHtml(entry.result)}">
|
|
21523
|
+
<td class="mono time">${escHtml(time)}</td>
|
|
21524
|
+
<td class="mono">${escHtml(entry.tool)}</td>
|
|
21525
|
+
<td class="mono">${escHtml(entry.server)}</td>
|
|
21526
|
+
<td class="tier tier-${escHtml(entry.tier)}">T${escHtml(entry.tier)}</td>
|
|
21527
|
+
<td class="result">${escHtml(entry.result)}</td>
|
|
21528
|
+
</tr>`;
|
|
21529
|
+
}).join("");
|
|
21530
|
+
const approvalItems = pending_approvals.length === 0 ? `<div class="empty-block">No pending approvals</div>` : pending_approvals.map((p) => `<article class="approval" data-id="${escHtml(p.id)}">
|
|
21531
|
+
<div class="approval-head">
|
|
21532
|
+
<span class="tier-chip tier-${escHtml(p.tier)}">Tier ${escHtml(p.tier)}</span>
|
|
21533
|
+
<span class="mono">${escHtml(p.operation)}</span>
|
|
21534
|
+
</div>
|
|
21535
|
+
<p class="approval-reason">${escHtml(p.reason)}</p>
|
|
21536
|
+
<div class="approval-actions">
|
|
21537
|
+
<button class="btn btn-allow" data-action="allow" data-id="${escHtml(p.id)}">Allow</button>
|
|
21538
|
+
<button class="btn btn-deny" data-action="deny" data-id="${escHtml(p.id)}">Deny</button>
|
|
21539
|
+
</div>
|
|
21540
|
+
</article>`).join("");
|
|
21541
|
+
const auditRows = audit.length === 0 ? `<tr class="empty"><td colspan="4">Audit log empty</td></tr>` : audit.map((entry) => `<tr data-kind="${escHtml(entry.layer)}" data-op="${escHtml(entry.operation)}">
|
|
21542
|
+
<td class="mono time">${escHtml(new Date(entry.timestamp).toLocaleTimeString())}</td>
|
|
21543
|
+
<td class="layer-pill">${escHtml(entry.layer.toUpperCase())}</td>
|
|
21544
|
+
<td class="mono">${escHtml(entry.operation)}</td>
|
|
21545
|
+
<td class="result-${escHtml(entry.result)}">${escHtml(entry.result)}</td>
|
|
21546
|
+
</tr>`).join("");
|
|
21547
|
+
const serverRows = upstream_servers.length === 0 ? `<li class="empty-block">No upstream servers connected</li>` : upstream_servers.map((s) => `<li class="server-row state-${escHtml(s.state)}">
|
|
21548
|
+
<span class="server-dot"></span>
|
|
21549
|
+
<span class="mono">${escHtml(s.name)}</span>
|
|
21550
|
+
<span class="server-meta">${escHtml(s.state)} \xB7 ${escHtml(s.tool_count)} tool${s.tool_count === 1 ? "" : "s"}</span>
|
|
21551
|
+
</li>`).join("");
|
|
21552
|
+
const initialSnapshot = JSON.stringify(snapshot).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
21553
|
+
return `<!DOCTYPE html>
|
|
21554
|
+
<html lang="en">
|
|
21555
|
+
<head>
|
|
21556
|
+
<meta charset="UTF-8">
|
|
21557
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
21558
|
+
<title>Sanctuary \u2014 Sovereignty Dashboard</title>
|
|
21559
|
+
<style>
|
|
21560
|
+
:root {
|
|
21561
|
+
--bg: #07080c;
|
|
21562
|
+
--bg-2: #0f1220;
|
|
21563
|
+
--surface: #131729;
|
|
21564
|
+
--surface-2: #1a1f36;
|
|
21565
|
+
--border: #26304d;
|
|
21566
|
+
--border-strong: #39436a;
|
|
21567
|
+
--ink: #eef1fb;
|
|
21568
|
+
--ink-dim: #b9c0dc;
|
|
21569
|
+
--ink-mute: #7e86a8;
|
|
21570
|
+
--indigo: #6e7bff;
|
|
21571
|
+
--indigo-deep: #3b4ad8;
|
|
21572
|
+
--green: #3ee08f;
|
|
21573
|
+
--green-deep: #168a4d;
|
|
21574
|
+
--amber: #f1c05a;
|
|
21575
|
+
--red: #ff6b7a;
|
|
21576
|
+
--violet: #a77bff;
|
|
21577
|
+
--radius: 14px;
|
|
21578
|
+
--radius-sm: 8px;
|
|
21579
|
+
--shadow: 0 14px 42px rgba(3, 6, 19, 0.45);
|
|
21580
|
+
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
21581
|
+
}
|
|
21582
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
21583
|
+
html, body { background: var(--bg); color: var(--ink); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, sans-serif; min-height: 100vh; -webkit-font-smoothing: antialiased; }
|
|
21584
|
+
body { background: radial-gradient(circle at 50% -200px, rgba(110, 123, 255, 0.18), transparent 60%), var(--bg); }
|
|
21585
|
+
a { color: var(--indigo); text-decoration: none; }
|
|
21586
|
+
button { font: inherit; cursor: pointer; }
|
|
21587
|
+
|
|
21588
|
+
.wrap { max-width: 1180px; margin: 0 auto; padding: 40px 32px 120px; }
|
|
21589
|
+
|
|
21590
|
+
/* \u2500\u2500 Top meta row \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
21591
|
+
.meta-row {
|
|
21592
|
+
display: flex;
|
|
21593
|
+
justify-content: space-between;
|
|
21594
|
+
align-items: center;
|
|
21595
|
+
color: var(--ink-mute);
|
|
21596
|
+
font-size: 12px;
|
|
21597
|
+
letter-spacing: 0.08em;
|
|
21598
|
+
text-transform: uppercase;
|
|
21599
|
+
margin-bottom: 8px;
|
|
21600
|
+
}
|
|
21601
|
+
.meta-row .mode-pill {
|
|
21602
|
+
padding: 4px 10px;
|
|
21603
|
+
border-radius: 999px;
|
|
21604
|
+
border: 1px solid var(--border);
|
|
21605
|
+
background: var(--surface);
|
|
21606
|
+
font-family: var(--mono);
|
|
21607
|
+
text-transform: none;
|
|
21608
|
+
letter-spacing: 0;
|
|
21609
|
+
font-size: 11px;
|
|
21610
|
+
color: var(--ink-dim);
|
|
21611
|
+
}
|
|
21612
|
+
|
|
21613
|
+
/* \u2500\u2500 Hero \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
21614
|
+
.hero {
|
|
21615
|
+
display: flex;
|
|
21616
|
+
flex-direction: column;
|
|
21617
|
+
align-items: center;
|
|
21618
|
+
text-align: center;
|
|
21619
|
+
padding: 48px 24px 56px;
|
|
21620
|
+
border-radius: 22px;
|
|
21621
|
+
background:
|
|
21622
|
+
radial-gradient(circle at 50% 0%, rgba(62, 224, 143, 0.08), transparent 70%),
|
|
21623
|
+
linear-gradient(180deg, var(--bg-2) 0%, var(--surface) 100%);
|
|
21624
|
+
border: 1px solid var(--border);
|
|
21625
|
+
box-shadow: var(--shadow);
|
|
21626
|
+
margin-bottom: 32px;
|
|
21627
|
+
position: relative;
|
|
21628
|
+
overflow: hidden;
|
|
21629
|
+
}
|
|
21630
|
+
.hero::after {
|
|
21631
|
+
content: "";
|
|
21632
|
+
position: absolute;
|
|
21633
|
+
inset: 0;
|
|
21634
|
+
background: radial-gradient(circle at 50% 100%, rgba(110, 123, 255, 0.10), transparent 60%);
|
|
21635
|
+
pointer-events: none;
|
|
21636
|
+
}
|
|
21637
|
+
.shield {
|
|
21638
|
+
width: 200px;
|
|
21639
|
+
height: 200px;
|
|
21640
|
+
position: relative;
|
|
21641
|
+
filter: drop-shadow(0 16px 32px rgba(62, 224, 143, 0.18));
|
|
21642
|
+
animation: hero-in 600ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
|
|
21643
|
+
}
|
|
21644
|
+
@keyframes hero-in {
|
|
21645
|
+
from { opacity: 0; transform: translateY(12px) scale(0.96); }
|
|
21646
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
21647
|
+
}
|
|
21648
|
+
.shield svg { width: 100%; height: 100%; display: block; }
|
|
21649
|
+
.shield.green .shield-ring { stroke: var(--green); }
|
|
21650
|
+
.shield.green .shield-core { fill: rgba(62, 224, 143, 0.14); }
|
|
21651
|
+
.shield.green .shield-mark { stroke: var(--green); }
|
|
21652
|
+
.shield.yellow .shield-ring { stroke: var(--amber); }
|
|
21653
|
+
.shield.yellow .shield-core { fill: rgba(241, 192, 90, 0.14); }
|
|
21654
|
+
.shield.yellow .shield-mark { stroke: var(--amber); }
|
|
21655
|
+
.shield.red .shield-ring { stroke: var(--red); }
|
|
21656
|
+
.shield.red .shield-core { fill: rgba(255, 107, 122, 0.16); }
|
|
21657
|
+
.shield.red .shield-mark { stroke: var(--red); }
|
|
21658
|
+
.shield .shield-ring {
|
|
21659
|
+
fill: none;
|
|
21660
|
+
stroke-width: 3;
|
|
21661
|
+
stroke-dasharray: 600;
|
|
21662
|
+
stroke-dashoffset: 0;
|
|
21663
|
+
transform-origin: center;
|
|
21664
|
+
transition: stroke 320ms ease;
|
|
21665
|
+
}
|
|
21666
|
+
.shield .shield-ring-bg { fill: none; stroke: rgba(255, 255, 255, 0.06); stroke-width: 3; }
|
|
21667
|
+
.shield .shield-mark { fill: none; stroke-width: 4; stroke-linecap: round; stroke-linejoin: round; transition: stroke 320ms ease; }
|
|
21668
|
+
|
|
21669
|
+
.hero h1 {
|
|
21670
|
+
font-size: 40px;
|
|
21671
|
+
font-weight: 650;
|
|
21672
|
+
letter-spacing: -0.02em;
|
|
21673
|
+
margin-top: 28px;
|
|
21674
|
+
position: relative;
|
|
21675
|
+
z-index: 1;
|
|
21676
|
+
}
|
|
21677
|
+
.hero .hero-sub {
|
|
21678
|
+
margin-top: 10px;
|
|
21679
|
+
color: var(--ink-dim);
|
|
21680
|
+
font-size: 15px;
|
|
21681
|
+
position: relative;
|
|
21682
|
+
z-index: 1;
|
|
21683
|
+
}
|
|
21684
|
+
.identity-line {
|
|
21685
|
+
margin-top: 22px;
|
|
21686
|
+
display: inline-flex;
|
|
21687
|
+
align-items: center;
|
|
21688
|
+
gap: 10px;
|
|
21689
|
+
padding: 10px 18px;
|
|
21690
|
+
border-radius: 999px;
|
|
21691
|
+
border: 1px solid var(--border);
|
|
21692
|
+
background: rgba(19, 23, 41, 0.7);
|
|
21693
|
+
font-family: var(--mono);
|
|
21694
|
+
font-size: 13px;
|
|
21695
|
+
color: var(--ink-dim);
|
|
21696
|
+
position: relative;
|
|
21697
|
+
z-index: 1;
|
|
21698
|
+
}
|
|
21699
|
+
.identity-line .name { color: var(--ink); font-weight: 500; font-family: -apple-system, BlinkMacSystemFont, Inter, sans-serif; letter-spacing: 0; }
|
|
21700
|
+
.identity-line .sep { color: var(--ink-mute); }
|
|
21701
|
+
.identity-line .did { color: var(--violet); }
|
|
21702
|
+
.identity-line .primary-flag {
|
|
21703
|
+
padding: 2px 8px;
|
|
21704
|
+
border-radius: 999px;
|
|
21705
|
+
background: rgba(110, 123, 255, 0.16);
|
|
21706
|
+
color: var(--indigo);
|
|
21707
|
+
font-size: 11px;
|
|
21708
|
+
text-transform: uppercase;
|
|
21709
|
+
letter-spacing: 0.1em;
|
|
21710
|
+
font-family: -apple-system, BlinkMacSystemFont, Inter, sans-serif;
|
|
21711
|
+
}
|
|
21712
|
+
|
|
21713
|
+
/* \u2500\u2500 Layer grid \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
21714
|
+
.layer-grid {
|
|
21715
|
+
display: grid;
|
|
21716
|
+
grid-template-columns: repeat(4, 1fr);
|
|
21717
|
+
gap: 16px;
|
|
21718
|
+
margin-bottom: 32px;
|
|
21719
|
+
}
|
|
21720
|
+
@media (max-width: 960px) { .layer-grid { grid-template-columns: repeat(2, 1fr); } }
|
|
21721
|
+
@media (max-width: 520px) { .layer-grid { grid-template-columns: 1fr; } }
|
|
21722
|
+
|
|
21723
|
+
.layer-card {
|
|
21724
|
+
padding: 20px;
|
|
21725
|
+
border-radius: var(--radius);
|
|
21726
|
+
background: var(--surface);
|
|
21727
|
+
border: 1px solid var(--border);
|
|
21728
|
+
transition: border 220ms ease, transform 220ms ease;
|
|
21729
|
+
}
|
|
21730
|
+
.layer-card:hover { border-color: var(--border-strong); transform: translateY(-1px); }
|
|
21731
|
+
|
|
21732
|
+
.layer-head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 14px; }
|
|
21733
|
+
.layer-dot { width: 12px; height: 12px; border-radius: 50%; margin-top: 6px; background: var(--ink-mute); box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.04); }
|
|
21734
|
+
.layer-full .layer-dot { background: var(--green); box-shadow: 0 0 0 3px rgba(62, 224, 143, 0.18); }
|
|
21735
|
+
.layer-degraded .layer-dot { background: var(--amber); box-shadow: 0 0 0 3px rgba(241, 192, 90, 0.18); }
|
|
21736
|
+
.layer-compromised .layer-dot { background: var(--red); box-shadow: 0 0 0 3px rgba(255, 107, 122, 0.18); }
|
|
21737
|
+
.layer-head h3 { font-size: 13px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-dim); margin-bottom: 4px; }
|
|
21738
|
+
.layer-headline { font-size: 15px; color: var(--ink); line-height: 1.35; }
|
|
21739
|
+
|
|
21740
|
+
.layer-detail { display: flex; flex-direction: column; gap: 8px; }
|
|
21741
|
+
.layer-detail > div { display: flex; justify-content: space-between; gap: 12px; font-size: 12px; }
|
|
21742
|
+
.layer-detail dt { color: var(--ink-mute); text-transform: uppercase; letter-spacing: 0.06em; font-size: 11px; }
|
|
21743
|
+
.layer-detail dd { color: var(--ink-dim); text-align: right; font-family: var(--mono); font-size: 12px; }
|
|
21744
|
+
|
|
21745
|
+
.layer-cta { margin-top: 6px; text-align: center; padding: 16px; border-radius: var(--radius-sm); background: var(--bg-2); border: 1px solid var(--border); }
|
|
21746
|
+
.score-block { display: flex; flex-direction: column; gap: 2px; }
|
|
21747
|
+
.score-value { font-size: 28px; font-weight: 650; color: var(--green); letter-spacing: -0.02em; }
|
|
21748
|
+
.score-label { font-size: 11px; text-transform: uppercase; color: var(--ink-mute); letter-spacing: 0.08em; }
|
|
21749
|
+
.claim-block { font-size: 13px; color: var(--violet); }
|
|
21750
|
+
|
|
21751
|
+
/* \u2500\u2500 L4 evidence widget (v0.9.1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
21752
|
+
.l4-evidence { margin-top: 12px; padding-top: 12px; border-top: 1px dashed var(--border); }
|
|
21753
|
+
.l4-evidence-summary { gap: 6px; margin-bottom: 10px; }
|
|
21754
|
+
.l4-evidence-summary dt { font-size: 10px; }
|
|
21755
|
+
.l4-evidence-summary dd { font-size: 11px; }
|
|
21756
|
+
.l4-deg-list { list-style: none; display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
|
|
21757
|
+
.l4-deg { padding: 8px 10px; border-radius: var(--radius-sm); background: var(--bg-2); border: 1px solid var(--border); font-size: 12px; }
|
|
21758
|
+
.l4-deg-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; margin-bottom: 3px; }
|
|
21759
|
+
.l4-deg-code { font-family: var(--mono); font-size: 11px; color: var(--ink); letter-spacing: 0.02em; }
|
|
21760
|
+
.l4-deg-sev { font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-mute); }
|
|
21761
|
+
.l4-deg-warning { border-color: rgba(241, 192, 90, 0.4); }
|
|
21762
|
+
.l4-deg-warning .l4-deg-sev { color: var(--amber); }
|
|
21763
|
+
.l4-deg-critical { border-color: rgba(255, 107, 122, 0.5); }
|
|
21764
|
+
.l4-deg-critical .l4-deg-sev { color: var(--red); }
|
|
21765
|
+
.l4-deg-info .l4-deg-sev { color: var(--indigo); }
|
|
21766
|
+
.l4-deg-desc { color: var(--ink-dim); line-height: 1.35; font-size: 11px; }
|
|
21767
|
+
.l4-deg-mit { color: var(--ink-mute); line-height: 1.35; font-size: 10px; margin-top: 3px; font-style: italic; }
|
|
21768
|
+
|
|
21769
|
+
/* \u2500\u2500 Section headers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
21770
|
+
.section { margin-bottom: 28px; }
|
|
21771
|
+
.section h2 {
|
|
21772
|
+
font-size: 13px;
|
|
21773
|
+
text-transform: uppercase;
|
|
21774
|
+
letter-spacing: 0.1em;
|
|
21775
|
+
color: var(--ink-dim);
|
|
21776
|
+
margin-bottom: 12px;
|
|
21777
|
+
display: flex;
|
|
21778
|
+
align-items: center;
|
|
21779
|
+
gap: 12px;
|
|
21780
|
+
}
|
|
21781
|
+
.section h2 .count {
|
|
21782
|
+
font-family: var(--mono);
|
|
21783
|
+
font-size: 11px;
|
|
21784
|
+
padding: 2px 8px;
|
|
21785
|
+
border-radius: 999px;
|
|
21786
|
+
background: var(--surface);
|
|
21787
|
+
border: 1px solid var(--border);
|
|
21788
|
+
color: var(--ink-mute);
|
|
21789
|
+
text-transform: none;
|
|
21790
|
+
letter-spacing: 0;
|
|
21791
|
+
}
|
|
21792
|
+
|
|
21793
|
+
/* \u2500\u2500 Approval queue \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
21794
|
+
.approval-list { display: flex; flex-direction: column; gap: 10px; }
|
|
21795
|
+
.approval {
|
|
21796
|
+
padding: 16px;
|
|
21797
|
+
background: var(--surface);
|
|
21798
|
+
border: 1px solid var(--amber);
|
|
21799
|
+
border-radius: var(--radius);
|
|
21800
|
+
box-shadow: 0 0 0 1px rgba(241, 192, 90, 0.18);
|
|
21801
|
+
animation: fade-up 260ms ease both;
|
|
21802
|
+
}
|
|
21803
|
+
@keyframes fade-up { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
|
|
21804
|
+
.approval-head { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; font-size: 14px; }
|
|
21805
|
+
.tier-chip { padding: 2px 8px; border-radius: 999px; font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; font-family: -apple-system, Inter, sans-serif; }
|
|
21806
|
+
.tier-chip.tier-1 { background: rgba(255, 107, 122, 0.16); color: var(--red); }
|
|
21807
|
+
.tier-chip.tier-2 { background: rgba(241, 192, 90, 0.16); color: var(--amber); }
|
|
21808
|
+
.approval-reason { font-size: 13px; color: var(--ink-dim); margin-bottom: 12px; }
|
|
21809
|
+
.approval-actions { display: flex; gap: 8px; }
|
|
21810
|
+
.btn {
|
|
21811
|
+
padding: 7px 14px;
|
|
21812
|
+
border-radius: var(--radius-sm);
|
|
21813
|
+
border: 1px solid var(--border);
|
|
21814
|
+
background: var(--surface-2);
|
|
21815
|
+
color: var(--ink);
|
|
21816
|
+
font-size: 13px;
|
|
21817
|
+
transition: all 160ms ease;
|
|
21818
|
+
}
|
|
21819
|
+
.btn:hover { border-color: var(--border-strong); background: #202641; }
|
|
21820
|
+
.btn-allow { background: var(--green-deep); border-color: var(--green-deep); color: white; }
|
|
21821
|
+
.btn-allow:hover { background: #1ba25b; border-color: #1ba25b; }
|
|
21822
|
+
.btn-deny { background: transparent; border-color: var(--red); color: var(--red); }
|
|
21823
|
+
.btn-deny:hover { background: rgba(255, 107, 122, 0.1); border-color: var(--red); }
|
|
21824
|
+
|
|
21825
|
+
/* \u2500\u2500 Tables \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
21826
|
+
.panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
|
|
21827
|
+
.panel-head { display: flex; justify-content: space-between; align-items: center; padding: 12px 18px; border-bottom: 1px solid var(--border); }
|
|
21828
|
+
.panel-head h3 { font-size: 12px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-dim); }
|
|
21829
|
+
.filter-row { display: flex; gap: 6px; }
|
|
21830
|
+
.filter-row button {
|
|
21831
|
+
padding: 4px 10px;
|
|
21832
|
+
font-size: 11px;
|
|
21833
|
+
border-radius: 999px;
|
|
21834
|
+
background: transparent;
|
|
21835
|
+
border: 1px solid var(--border);
|
|
21836
|
+
color: var(--ink-mute);
|
|
21837
|
+
text-transform: uppercase;
|
|
21838
|
+
letter-spacing: 0.06em;
|
|
21839
|
+
transition: all 140ms ease;
|
|
21840
|
+
}
|
|
21841
|
+
.filter-row button.active, .filter-row button:hover { color: var(--ink); border-color: var(--border-strong); background: var(--surface-2); }
|
|
21842
|
+
|
|
21843
|
+
table { width: 100%; border-collapse: collapse; }
|
|
21844
|
+
th, td { padding: 10px 18px; text-align: left; font-size: 13px; border-bottom: 1px solid var(--border); }
|
|
21845
|
+
th { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-mute); font-weight: 500; background: var(--bg-2); }
|
|
21846
|
+
tr:last-child td { border-bottom: none; }
|
|
21847
|
+
tr.empty td { text-align: center; color: var(--ink-mute); padding: 32px 18px; }
|
|
21848
|
+
.mono { font-family: var(--mono); font-size: 12px; }
|
|
21849
|
+
.time { color: var(--ink-mute); white-space: nowrap; }
|
|
21850
|
+
.tier { text-align: center; font-family: var(--mono); font-size: 11px; font-weight: 600; }
|
|
21851
|
+
.tier-1 { color: var(--red); }
|
|
21852
|
+
.tier-2 { color: var(--amber); }
|
|
21853
|
+
.tier-3 { color: var(--green); }
|
|
21854
|
+
.result { font-family: var(--mono); font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; }
|
|
21855
|
+
.result-allowed, .result-success { color: var(--green); }
|
|
21856
|
+
.result-denied, .result-failure { color: var(--red); }
|
|
21857
|
+
.result-approved { color: var(--indigo); }
|
|
21858
|
+
.result-pending { color: var(--amber); }
|
|
21859
|
+
.layer-pill { font-family: var(--mono); font-size: 11px; padding: 2px 8px; border-radius: 999px; background: var(--surface-2); color: var(--ink-dim); display: inline-block; }
|
|
21860
|
+
|
|
21861
|
+
/* \u2500\u2500 Upstream servers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
21862
|
+
.server-list { list-style: none; display: flex; flex-direction: column; gap: 8px; }
|
|
21863
|
+
.server-row { display: flex; align-items: center; gap: 10px; padding: 10px 14px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: 13px; }
|
|
21864
|
+
.server-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ink-mute); }
|
|
21865
|
+
.server-row.state-connected .server-dot { background: var(--green); }
|
|
21866
|
+
.server-row.state-connecting .server-dot { background: var(--amber); }
|
|
21867
|
+
.server-row.state-disconnected .server-dot, .server-row.state-error .server-dot { background: var(--red); }
|
|
21868
|
+
.server-meta { margin-left: auto; font-size: 12px; color: var(--ink-mute); font-family: var(--mono); }
|
|
21869
|
+
|
|
21870
|
+
.empty-block { padding: 18px; color: var(--ink-mute); text-align: center; font-size: 13px; background: var(--surface); border: 1px dashed var(--border); border-radius: var(--radius-sm); }
|
|
21871
|
+
|
|
21872
|
+
/* \u2500\u2500 Audit collapsible \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
21873
|
+
details.audit-details { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
|
|
21874
|
+
details.audit-details summary { cursor: pointer; list-style: none; padding: 14px 18px; display: flex; align-items: center; justify-content: space-between; }
|
|
21875
|
+
details.audit-details summary::-webkit-details-marker { display: none; }
|
|
21876
|
+
details.audit-details summary h3 { font-size: 12px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-dim); }
|
|
21877
|
+
details.audit-details summary .caret { color: var(--ink-mute); font-family: var(--mono); }
|
|
21878
|
+
details.audit-details[open] .caret { transform: rotate(90deg); display: inline-block; }
|
|
21879
|
+
details.audit-details .audit-filters { display: flex; gap: 6px; padding: 0 18px 12px; border-bottom: 1px solid var(--border); }
|
|
21880
|
+
</style>
|
|
21881
|
+
</head>
|
|
21882
|
+
<body>
|
|
21883
|
+
<div class="wrap">
|
|
21884
|
+
<div class="meta-row">
|
|
21885
|
+
<span>Sanctuary Framework</span>
|
|
21886
|
+
<span class="mode-pill" id="mode-pill">${escHtml(snapshot.mode)} \xB7 v${escHtml(snapshot.server_version)}</span>
|
|
21887
|
+
</div>
|
|
21888
|
+
|
|
21889
|
+
<section class="hero">
|
|
21890
|
+
<div class="shield ${escHtml(overall.light)}" id="shield">
|
|
21891
|
+
<svg viewBox="0 0 200 200" aria-hidden="true">
|
|
21892
|
+
<circle class="shield-ring-bg" cx="100" cy="100" r="92"></circle>
|
|
21893
|
+
<circle class="shield-ring" cx="100" cy="100" r="92"></circle>
|
|
21894
|
+
<circle class="shield-core" cx="100" cy="100" r="80" fill="rgba(255,255,255,0.02)"></circle>
|
|
21895
|
+
<path class="shield-mark" d="M100 52 L132 68 L132 108 C132 130 118 144 100 150 C82 144 68 130 68 108 L68 68 Z"></path>
|
|
21896
|
+
<path class="shield-mark" d="M85 102 L96 113 L118 90"></path>
|
|
21897
|
+
</svg>
|
|
21898
|
+
</div>
|
|
21899
|
+
<h1 id="hero-copy">${escHtml(HERO_COPY)}</h1>
|
|
21900
|
+
<p class="hero-sub" id="hero-sub">${escHtml(overall.headline)}</p>
|
|
21901
|
+
<div class="identity-line" id="identity-line">
|
|
21902
|
+
<span class="name" id="agent-name">${escHtml(agent.display_name)}</span>
|
|
21903
|
+
<span class="sep">\xB7</span>
|
|
21904
|
+
<span class="did" id="agent-did">${escHtml(agent.did_fingerprint ?? "unclaimed")}</span>
|
|
21905
|
+
${agent.primary_identity_id ? `<span class="primary-flag">Primary</span>` : ""}
|
|
21906
|
+
</div>
|
|
21907
|
+
</section>
|
|
21908
|
+
|
|
21909
|
+
<div class="layer-grid" id="layer-grid">
|
|
21910
|
+
${l1Card(layers.l1)}
|
|
21911
|
+
${l2Card(layers.l2)}
|
|
21912
|
+
${l3Card(layers.l3)}
|
|
21913
|
+
${l4Card(layers.l4)}
|
|
21914
|
+
</div>
|
|
21915
|
+
|
|
21916
|
+
<section class="section" id="approval-section">
|
|
21917
|
+
<h2>Needs approval <span class="count" id="approval-count">${pending_approvals.length}</span></h2>
|
|
21918
|
+
<div class="approval-list" id="approval-list">${approvalItems}</div>
|
|
21919
|
+
</section>
|
|
21920
|
+
|
|
21921
|
+
<section class="section">
|
|
21922
|
+
<h2>Upstream servers <span class="count">${upstream_servers.length}</span></h2>
|
|
21923
|
+
<ul class="server-list" id="server-list">${serverRows}</ul>
|
|
21924
|
+
</section>
|
|
21925
|
+
|
|
21926
|
+
<section class="section">
|
|
21927
|
+
<h2>Live activity <span class="count" id="activity-count">${activity.length}</span></h2>
|
|
21928
|
+
<div class="panel">
|
|
21929
|
+
<div class="panel-head"><h3>Recent tool calls</h3></div>
|
|
21930
|
+
<table>
|
|
21931
|
+
<thead><tr><th>Time</th><th>Tool</th><th>Server</th><th>Tier</th><th>Result</th></tr></thead>
|
|
21932
|
+
<tbody id="activity-body">${activityRows}</tbody>
|
|
21933
|
+
</table>
|
|
21934
|
+
</div>
|
|
21935
|
+
</section>
|
|
21936
|
+
|
|
21937
|
+
<section class="section">
|
|
21938
|
+
<details class="audit-details">
|
|
21939
|
+
<summary>
|
|
21940
|
+
<h3>Audit trail <span class="count" id="audit-count">${audit.length}</span></h3>
|
|
21941
|
+
<span class="caret">\u25B8</span>
|
|
21942
|
+
</summary>
|
|
21943
|
+
<div class="audit-filters">
|
|
21944
|
+
<div class="filter-row" id="audit-filter">
|
|
21945
|
+
<button class="active" data-filter="all">All</button>
|
|
21946
|
+
<button data-filter="l1">Cognitive</button>
|
|
21947
|
+
<button data-filter="l2">Operational</button>
|
|
21948
|
+
<button data-filter="l3">Disclosure</button>
|
|
21949
|
+
<button data-filter="l4">Reputation</button>
|
|
21950
|
+
</div>
|
|
21951
|
+
</div>
|
|
21952
|
+
<table>
|
|
21953
|
+
<thead><tr><th>Time</th><th>Layer</th><th>Operation</th><th>Result</th></tr></thead>
|
|
21954
|
+
<tbody id="audit-body">${auditRows}</tbody>
|
|
21955
|
+
</table>
|
|
21956
|
+
</details>
|
|
21957
|
+
</section>
|
|
21958
|
+
</div>
|
|
21959
|
+
|
|
21960
|
+
<script>
|
|
21961
|
+
(() => {
|
|
21962
|
+
const INITIAL_SNAPSHOT = ${initialSnapshot};
|
|
21963
|
+
const AUTH_TOKEN = ${options.authToken ? JSON.stringify(options.authToken) : "null"};
|
|
21964
|
+
const AUTH_HEADER = AUTH_TOKEN ? { "Authorization": "Bearer " + AUTH_TOKEN } : {};
|
|
21965
|
+
const AUTH_QS = AUTH_TOKEN ? "?token=" + encodeURIComponent(AUTH_TOKEN) : "";
|
|
21966
|
+
|
|
21967
|
+
let snapshot = INITIAL_SNAPSHOT;
|
|
21968
|
+
|
|
21969
|
+
function esc(value) {
|
|
21970
|
+
if (value == null) return "";
|
|
21971
|
+
return String(value)
|
|
21972
|
+
.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
|
21973
|
+
.replace(/"/g, """).replace(/'/g, "'");
|
|
21974
|
+
}
|
|
21975
|
+
function fmtTime(iso) {
|
|
21976
|
+
try { return new Date(iso).toLocaleTimeString(); } catch { return iso; }
|
|
21977
|
+
}
|
|
21978
|
+
|
|
21979
|
+
function renderShield(light, headline) {
|
|
21980
|
+
const shield = document.getElementById("shield");
|
|
21981
|
+
if (!shield) return;
|
|
21982
|
+
shield.classList.remove("green", "yellow", "red");
|
|
21983
|
+
shield.classList.add(light);
|
|
21984
|
+
document.getElementById("hero-sub").textContent = headline;
|
|
21985
|
+
}
|
|
21986
|
+
|
|
21987
|
+
function renderActivity(entries) {
|
|
21988
|
+
const body = document.getElementById("activity-body");
|
|
21989
|
+
const count = document.getElementById("activity-count");
|
|
21990
|
+
if (!body) return;
|
|
21991
|
+
count.textContent = String(entries.length);
|
|
21992
|
+
if (!entries.length) {
|
|
21993
|
+
body.innerHTML = '<tr class="empty"><td colspan="5">Waiting for tool calls\u2026</td></tr>';
|
|
21994
|
+
return;
|
|
21995
|
+
}
|
|
21996
|
+
body.innerHTML = entries.map(e => (
|
|
21997
|
+
'<tr class="result-' + esc(e.result) + '">' +
|
|
21998
|
+
'<td class="mono time">' + esc(fmtTime(e.timestamp)) + '</td>' +
|
|
21999
|
+
'<td class="mono">' + esc(e.tool) + '</td>' +
|
|
22000
|
+
'<td class="mono">' + esc(e.server) + '</td>' +
|
|
22001
|
+
'<td class="tier tier-' + esc(e.tier) + '">T' + esc(e.tier) + '</td>' +
|
|
22002
|
+
'<td class="result">' + esc(e.result) + '</td>' +
|
|
22003
|
+
'</tr>'
|
|
22004
|
+
)).join("");
|
|
22005
|
+
}
|
|
22006
|
+
|
|
22007
|
+
function renderApprovals(list) {
|
|
22008
|
+
const container = document.getElementById("approval-list");
|
|
22009
|
+
const count = document.getElementById("approval-count");
|
|
22010
|
+
if (!container) return;
|
|
22011
|
+
count.textContent = String(list.length);
|
|
22012
|
+
if (!list.length) {
|
|
22013
|
+
container.innerHTML = '<div class="empty-block">No pending approvals</div>';
|
|
22014
|
+
return;
|
|
22015
|
+
}
|
|
22016
|
+
container.innerHTML = list.map(p => (
|
|
22017
|
+
'<article class="approval" data-id="' + esc(p.id) + '">' +
|
|
22018
|
+
'<div class="approval-head">' +
|
|
22019
|
+
'<span class="tier-chip tier-' + esc(p.tier) + '">Tier ' + esc(p.tier) + '</span>' +
|
|
22020
|
+
'<span class="mono">' + esc(p.operation) + '</span>' +
|
|
22021
|
+
'</div>' +
|
|
22022
|
+
'<p class="approval-reason">' + esc(p.reason) + '</p>' +
|
|
22023
|
+
'<div class="approval-actions">' +
|
|
22024
|
+
'<button class="btn btn-allow" data-action="allow" data-id="' + esc(p.id) + '">Allow</button>' +
|
|
22025
|
+
'<button class="btn btn-deny" data-action="deny" data-id="' + esc(p.id) + '">Deny</button>' +
|
|
22026
|
+
'</div>' +
|
|
22027
|
+
'</article>'
|
|
22028
|
+
)).join("");
|
|
22029
|
+
wireApprovalButtons();
|
|
22030
|
+
}
|
|
22031
|
+
|
|
22032
|
+
function renderAudit(entries, filter) {
|
|
22033
|
+
filter = filter || "all";
|
|
22034
|
+
const body = document.getElementById("audit-body");
|
|
22035
|
+
const count = document.getElementById("audit-count");
|
|
22036
|
+
if (!body) return;
|
|
22037
|
+
const visible = filter === "all" ? entries : entries.filter(e => e.layer === filter);
|
|
22038
|
+
count.textContent = String(visible.length);
|
|
22039
|
+
if (!visible.length) {
|
|
22040
|
+
body.innerHTML = '<tr class="empty"><td colspan="4">Audit log empty</td></tr>';
|
|
22041
|
+
return;
|
|
22042
|
+
}
|
|
22043
|
+
body.innerHTML = visible.map(e => (
|
|
22044
|
+
'<tr data-kind="' + esc(e.layer) + '" data-op="' + esc(e.operation) + '">' +
|
|
22045
|
+
'<td class="mono time">' + esc(fmtTime(e.timestamp)) + '</td>' +
|
|
22046
|
+
'<td><span class="layer-pill">' + esc(String(e.layer).toUpperCase()) + '</span></td>' +
|
|
22047
|
+
'<td class="mono">' + esc(e.operation) + '</td>' +
|
|
22048
|
+
'<td class="result-' + esc(e.result) + '">' + esc(e.result) + '</td>' +
|
|
22049
|
+
'</tr>'
|
|
22050
|
+
)).join("");
|
|
22051
|
+
}
|
|
22052
|
+
|
|
22053
|
+
function renderAll(snap) {
|
|
22054
|
+
snapshot = snap;
|
|
22055
|
+
renderShield(snap.overall.light, snap.overall.headline);
|
|
22056
|
+
const mp = document.getElementById("mode-pill");
|
|
22057
|
+
if (mp) mp.textContent = snap.mode + " \xB7 v" + snap.server_version;
|
|
22058
|
+
document.getElementById("agent-name").textContent = snap.agent.display_name;
|
|
22059
|
+
document.getElementById("agent-did").textContent = snap.agent.did_fingerprint || "unclaimed";
|
|
22060
|
+
renderActivity(snap.activity);
|
|
22061
|
+
renderApprovals(snap.pending_approvals);
|
|
22062
|
+
renderAudit(snap.audit, currentAuditFilter());
|
|
22063
|
+
}
|
|
22064
|
+
|
|
22065
|
+
function currentAuditFilter() {
|
|
22066
|
+
const active = document.querySelector("#audit-filter button.active");
|
|
22067
|
+
return active ? active.dataset.filter : "all";
|
|
22068
|
+
}
|
|
22069
|
+
|
|
22070
|
+
function wireApprovalButtons() {
|
|
22071
|
+
document.querySelectorAll("#approval-list button[data-action]").forEach(btn => {
|
|
22072
|
+
btn.addEventListener("click", async () => {
|
|
22073
|
+
const id = btn.dataset.id;
|
|
22074
|
+
const action = btn.dataset.action;
|
|
22075
|
+
btn.disabled = true;
|
|
22076
|
+
try {
|
|
22077
|
+
await fetch("/api/approvals/" + encodeURIComponent(id) + "/" + action + AUTH_QS, {
|
|
22078
|
+
method: "POST", headers: AUTH_HEADER
|
|
22079
|
+
});
|
|
22080
|
+
} catch {}
|
|
22081
|
+
await refreshSnapshot();
|
|
22082
|
+
});
|
|
22083
|
+
});
|
|
22084
|
+
}
|
|
22085
|
+
|
|
22086
|
+
function wireAuditFilter() {
|
|
22087
|
+
document.querySelectorAll("#audit-filter button").forEach(btn => {
|
|
22088
|
+
btn.addEventListener("click", () => {
|
|
22089
|
+
document.querySelectorAll("#audit-filter button").forEach(b => b.classList.remove("active"));
|
|
22090
|
+
btn.classList.add("active");
|
|
22091
|
+
renderAudit(snapshot.audit, btn.dataset.filter);
|
|
22092
|
+
});
|
|
22093
|
+
});
|
|
22094
|
+
}
|
|
22095
|
+
|
|
22096
|
+
async function refreshSnapshot() {
|
|
22097
|
+
try {
|
|
22098
|
+
const res = await fetch("/api/snapshot" + AUTH_QS, { headers: AUTH_HEADER });
|
|
22099
|
+
if (!res.ok) return;
|
|
22100
|
+
const snap = await res.json();
|
|
22101
|
+
renderAll(snap);
|
|
22102
|
+
} catch {}
|
|
22103
|
+
}
|
|
22104
|
+
|
|
22105
|
+
function connectSSE() {
|
|
22106
|
+
try {
|
|
22107
|
+
const es = new EventSource("/api/stream" + AUTH_QS);
|
|
22108
|
+
es.addEventListener("snapshot", (ev) => {
|
|
22109
|
+
try { renderAll(JSON.parse(ev.data)); } catch {}
|
|
22110
|
+
});
|
|
22111
|
+
es.addEventListener("activity", () => refreshSnapshot());
|
|
22112
|
+
es.addEventListener("approval", () => refreshSnapshot());
|
|
22113
|
+
es.onerror = () => { es.close(); setTimeout(connectSSE, 3000); };
|
|
22114
|
+
} catch {}
|
|
22115
|
+
}
|
|
22116
|
+
|
|
22117
|
+
wireApprovalButtons();
|
|
22118
|
+
wireAuditFilter();
|
|
22119
|
+
connectSSE();
|
|
22120
|
+
})();
|
|
22121
|
+
</script>
|
|
22122
|
+
</body>
|
|
22123
|
+
</html>`;
|
|
22124
|
+
}
|
|
22125
|
+
|
|
22126
|
+
// src/dashboard/api.ts
|
|
22127
|
+
function constantTimeEquals(a, b) {
|
|
22128
|
+
if (a.length !== b.length) return false;
|
|
22129
|
+
let diff = 0;
|
|
22130
|
+
for (let i = 0; i < a.length; i++) {
|
|
22131
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
22132
|
+
}
|
|
22133
|
+
return diff === 0;
|
|
22134
|
+
}
|
|
22135
|
+
function extractToken(req, url) {
|
|
22136
|
+
const header = req.headers.authorization;
|
|
22137
|
+
if (header && header.startsWith("Bearer ")) {
|
|
22138
|
+
return header.slice(7).trim();
|
|
22139
|
+
}
|
|
22140
|
+
const q = url.searchParams.get("token");
|
|
22141
|
+
return q ?? null;
|
|
22142
|
+
}
|
|
22143
|
+
function isAuthorized(deps, req, url) {
|
|
22144
|
+
if (!deps.authToken) return true;
|
|
22145
|
+
const token = extractToken(req, url);
|
|
22146
|
+
if (!token) return false;
|
|
22147
|
+
return constantTimeEquals(token, deps.authToken);
|
|
22148
|
+
}
|
|
22149
|
+
function writeJSON(res, status, payload) {
|
|
22150
|
+
res.writeHead(status, {
|
|
22151
|
+
"Content-Type": "application/json",
|
|
22152
|
+
"Cache-Control": "no-store"
|
|
22153
|
+
});
|
|
22154
|
+
res.end(JSON.stringify(payload));
|
|
22155
|
+
}
|
|
22156
|
+
function writeText(res, status, body, contentType = "text/plain") {
|
|
22157
|
+
res.writeHead(status, {
|
|
22158
|
+
"Content-Type": contentType,
|
|
22159
|
+
"Cache-Control": "no-store"
|
|
22160
|
+
});
|
|
22161
|
+
res.end(body);
|
|
22162
|
+
}
|
|
22163
|
+
async function handleRequest(deps, req, res) {
|
|
22164
|
+
const host = req.headers.host || "localhost";
|
|
22165
|
+
const url = new URL(req.url ?? "/", `http://${host}`);
|
|
22166
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
22167
|
+
const path = url.pathname;
|
|
22168
|
+
if (!isAuthorized(deps, req, url)) {
|
|
22169
|
+
writeJSON(res, 401, { error: "unauthorized" });
|
|
22170
|
+
return true;
|
|
22171
|
+
}
|
|
22172
|
+
if (method === "GET" && path === "/api/health") {
|
|
22173
|
+
writeJSON(res, 200, { ok: true, mode: deps.sources.mode });
|
|
22174
|
+
return true;
|
|
22175
|
+
}
|
|
22176
|
+
if (method === "GET" && (path === "/" || path === "/index.html")) {
|
|
22177
|
+
const snapshot = await getProtectionSnapshot(deps.sources);
|
|
22178
|
+
const html = renderDashboardHTML({ snapshot, authToken: deps.authToken });
|
|
22179
|
+
writeText(res, 200, html, "text/html; charset=utf-8");
|
|
22180
|
+
return true;
|
|
22181
|
+
}
|
|
22182
|
+
if (method === "GET" && path === "/api/snapshot") {
|
|
22183
|
+
const snapshot = await getProtectionSnapshot(deps.sources);
|
|
22184
|
+
writeJSON(res, 200, snapshot);
|
|
22185
|
+
return true;
|
|
22186
|
+
}
|
|
22187
|
+
const approvalMatch = /^\/api\/approvals\/([^/]+)\/(allow|deny)$/.exec(path);
|
|
22188
|
+
if (method === "POST" && approvalMatch) {
|
|
22189
|
+
const id = decodeURIComponent(approvalMatch[1]);
|
|
22190
|
+
const action = approvalMatch[2];
|
|
22191
|
+
if (!deps.approvals) {
|
|
22192
|
+
writeJSON(res, 503, { error: "approvals_unavailable" });
|
|
22193
|
+
return true;
|
|
22194
|
+
}
|
|
22195
|
+
const handler = action === "allow" ? deps.approvals.allow : deps.approvals.deny;
|
|
22196
|
+
try {
|
|
22197
|
+
const ok = await handler(id);
|
|
22198
|
+
writeJSON(res, ok ? 200 : 404, { id, action, ok });
|
|
22199
|
+
} catch (err) {
|
|
22200
|
+
writeJSON(res, 500, { error: "approval_failed", message: err.message });
|
|
22201
|
+
}
|
|
22202
|
+
return true;
|
|
22203
|
+
}
|
|
22204
|
+
if (method === "GET" && path === "/api/stream") {
|
|
22205
|
+
await handleStream(deps, res);
|
|
22206
|
+
return true;
|
|
22207
|
+
}
|
|
22208
|
+
return false;
|
|
22209
|
+
}
|
|
22210
|
+
async function handleStream(deps, res) {
|
|
22211
|
+
res.writeHead(200, {
|
|
22212
|
+
"Content-Type": "text/event-stream",
|
|
22213
|
+
"Cache-Control": "no-cache, no-transform",
|
|
22214
|
+
Connection: "keep-alive",
|
|
22215
|
+
"X-Accel-Buffering": "no"
|
|
22216
|
+
});
|
|
22217
|
+
const snapshot = await getProtectionSnapshot(deps.sources);
|
|
22218
|
+
res.write(`event: snapshot
|
|
22219
|
+
data: ${JSON.stringify(snapshot)}
|
|
22220
|
+
|
|
22221
|
+
`);
|
|
22222
|
+
const unsubscribe = deps.onEvent ? deps.onEvent((event) => {
|
|
22223
|
+
try {
|
|
22224
|
+
res.write(`event: ${event.type}
|
|
22225
|
+
data: ${JSON.stringify(event.data)}
|
|
22226
|
+
|
|
22227
|
+
`);
|
|
22228
|
+
} catch {
|
|
22229
|
+
}
|
|
22230
|
+
}) : () => {
|
|
22231
|
+
};
|
|
22232
|
+
const keepAlive = setInterval(() => {
|
|
22233
|
+
try {
|
|
22234
|
+
res.write(": keepalive\n\n");
|
|
22235
|
+
} catch {
|
|
22236
|
+
}
|
|
22237
|
+
}, 25e3);
|
|
22238
|
+
const cleanup = () => {
|
|
22239
|
+
clearInterval(keepAlive);
|
|
22240
|
+
unsubscribe();
|
|
22241
|
+
};
|
|
22242
|
+
res.on("close", cleanup);
|
|
22243
|
+
res.on("error", cleanup);
|
|
22244
|
+
}
|
|
22245
|
+
|
|
22246
|
+
// src/dashboard/server.ts
|
|
22247
|
+
var DEFAULT_PORT = 3501;
|
|
22248
|
+
var DEFAULT_HOST = "127.0.0.1";
|
|
22249
|
+
async function startDashboardServer(options) {
|
|
22250
|
+
const port = options.port ?? DEFAULT_PORT;
|
|
22251
|
+
const host = options.host ?? DEFAULT_HOST;
|
|
22252
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
22253
|
+
const onEvent = (listener) => {
|
|
22254
|
+
listeners.add(listener);
|
|
22255
|
+
return () => listeners.delete(listener);
|
|
22256
|
+
};
|
|
22257
|
+
const publish = (event) => {
|
|
22258
|
+
for (const listener of listeners) {
|
|
22259
|
+
try {
|
|
22260
|
+
listener(event);
|
|
22261
|
+
} catch {
|
|
22262
|
+
}
|
|
22263
|
+
}
|
|
22264
|
+
};
|
|
22265
|
+
const deps = {
|
|
22266
|
+
sources: options.sources,
|
|
22267
|
+
authToken: options.authToken,
|
|
22268
|
+
approvals: options.approvals,
|
|
22269
|
+
onEvent
|
|
22270
|
+
};
|
|
22271
|
+
const server = http.createServer(async (req, res) => {
|
|
22272
|
+
try {
|
|
22273
|
+
const served = await handleRequest(deps, req, res);
|
|
22274
|
+
if (!served) {
|
|
22275
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
22276
|
+
res.end(JSON.stringify({ error: "not_found", path: req.url }));
|
|
22277
|
+
}
|
|
22278
|
+
} catch (err) {
|
|
22279
|
+
try {
|
|
22280
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
22281
|
+
res.end(JSON.stringify({ error: "internal", message: err.message }));
|
|
22282
|
+
} catch {
|
|
22283
|
+
}
|
|
22284
|
+
}
|
|
22285
|
+
});
|
|
22286
|
+
await new Promise((resolve, reject) => {
|
|
22287
|
+
server.once("error", reject);
|
|
22288
|
+
server.listen(port, host, () => {
|
|
22289
|
+
server.off("error", reject);
|
|
22290
|
+
resolve();
|
|
22291
|
+
});
|
|
22292
|
+
});
|
|
22293
|
+
const actualPort = (() => {
|
|
22294
|
+
const addr = server.address();
|
|
22295
|
+
if (addr && typeof addr === "object") return addr.port;
|
|
22296
|
+
return port;
|
|
22297
|
+
})();
|
|
22298
|
+
const url = `http://${host}:${actualPort}`;
|
|
22299
|
+
return {
|
|
22300
|
+
url,
|
|
22301
|
+
port: actualPort,
|
|
22302
|
+
host,
|
|
22303
|
+
stop: () => new Promise((resolve, reject) => {
|
|
22304
|
+
server.close((err) => err ? reject(err) : resolve());
|
|
22305
|
+
}),
|
|
22306
|
+
publish,
|
|
22307
|
+
publishActivity: (entry) => publish({ type: "activity", data: entry }),
|
|
22308
|
+
publishApproval: (approval) => publish({ type: "approval", data: approval })
|
|
22309
|
+
};
|
|
22310
|
+
}
|
|
22311
|
+
|
|
22312
|
+
// src/dashboard/index.ts
|
|
22313
|
+
async function startDashboard(options) {
|
|
22314
|
+
const activity = options.initialActivity ? [...options.initialActivity] : [];
|
|
22315
|
+
const pending = options.initialPendingApprovals ? [...options.initialPendingApprovals] : [];
|
|
22316
|
+
const sources = {
|
|
22317
|
+
mode: options.mode,
|
|
22318
|
+
server_version: options.serverVersion,
|
|
22319
|
+
...options.auditLog ? { auditLog: options.auditLog } : {},
|
|
22320
|
+
...options.identityManager ? { identityManager: options.identityManager } : {},
|
|
22321
|
+
...options.clientManager ? { clientManager: options.clientManager } : {},
|
|
22322
|
+
...options.baseline ? { baseline: options.baseline } : {},
|
|
22323
|
+
...options.policy ? { policy: options.policy } : {},
|
|
22324
|
+
...options.reputation ? { reputation: options.reputation } : {},
|
|
22325
|
+
...options.teeAvailable != null ? { teeAvailable: options.teeAvailable } : {},
|
|
22326
|
+
...options.l4Evidence ? { l4Evidence: options.l4Evidence } : {},
|
|
22327
|
+
activity,
|
|
22328
|
+
pendingApprovals: pending
|
|
22329
|
+
};
|
|
22330
|
+
const serverOpts = {
|
|
22331
|
+
mode: options.mode,
|
|
22332
|
+
sources,
|
|
22333
|
+
...options.port != null ? { port: options.port } : {},
|
|
22334
|
+
...options.host ? { host: options.host } : {},
|
|
22335
|
+
...options.authToken ? { authToken: options.authToken } : {},
|
|
22336
|
+
...options.approvals ? { approvals: options.approvals } : {}
|
|
22337
|
+
};
|
|
22338
|
+
const handle = await startDashboardServer(serverOpts);
|
|
22339
|
+
const wrapped = {
|
|
22340
|
+
...handle,
|
|
22341
|
+
publishActivity: (entry) => {
|
|
22342
|
+
activity.unshift(entry);
|
|
22343
|
+
if (activity.length > 50) activity.length = 50;
|
|
22344
|
+
handle.publishActivity(entry);
|
|
22345
|
+
},
|
|
22346
|
+
publishApproval: (approval) => {
|
|
22347
|
+
pending.push(approval);
|
|
22348
|
+
handle.publishApproval(approval);
|
|
22349
|
+
}
|
|
22350
|
+
};
|
|
22351
|
+
return wrapped;
|
|
22352
|
+
}
|
|
22353
|
+
|
|
20854
22354
|
// src/index.ts
|
|
20855
22355
|
async function createSanctuaryServer(options) {
|
|
20856
22356
|
const config = await loadConfig(options?.configPath);
|
|
20857
22357
|
await promises.mkdir(config.storage_path, { recursive: true, mode: 448 });
|
|
22358
|
+
await tightenStoragePermissions(config.storage_path);
|
|
20858
22359
|
const storage = options?.storage ?? new FilesystemStorage(
|
|
20859
22360
|
`${config.storage_path}/state`
|
|
20860
22361
|
);
|
|
@@ -21174,12 +22675,6 @@ async function createSanctuaryServer(options) {
|
|
|
21174
22675
|
}
|
|
21175
22676
|
};
|
|
21176
22677
|
const { tools: l3Tools } = createL3Tools(storage, masterKey, auditLog);
|
|
21177
|
-
const { tools: shrTools } = createSHRTools(
|
|
21178
|
-
config,
|
|
21179
|
-
identityManager,
|
|
21180
|
-
masterKey,
|
|
21181
|
-
auditLog
|
|
21182
|
-
);
|
|
21183
22678
|
const { tools: handshakeTools, handshakeResults } = createHandshakeTools(
|
|
21184
22679
|
config,
|
|
21185
22680
|
identityManager,
|
|
@@ -21190,7 +22685,7 @@ async function createSanctuaryServer(options) {
|
|
|
21190
22685
|
verascoreUrl: config.verascore.url
|
|
21191
22686
|
}
|
|
21192
22687
|
);
|
|
21193
|
-
const { tools: l4Tools} = createL4Tools(
|
|
22688
|
+
const { tools: l4Tools, reputationStore } = createL4Tools(
|
|
21194
22689
|
storage,
|
|
21195
22690
|
masterKey,
|
|
21196
22691
|
identityManager,
|
|
@@ -21198,6 +22693,13 @@ async function createSanctuaryServer(options) {
|
|
|
21198
22693
|
handshakeResults,
|
|
21199
22694
|
config.verascore.url
|
|
21200
22695
|
);
|
|
22696
|
+
const { tools: shrTools } = createSHRTools(
|
|
22697
|
+
config,
|
|
22698
|
+
identityManager,
|
|
22699
|
+
masterKey,
|
|
22700
|
+
auditLog,
|
|
22701
|
+
reputationStore
|
|
22702
|
+
);
|
|
21201
22703
|
const { tools: federationTools } = createFederationTools(
|
|
21202
22704
|
auditLog,
|
|
21203
22705
|
handshakeResults
|
|
@@ -21288,7 +22790,8 @@ async function createSanctuaryServer(options) {
|
|
|
21288
22790
|
masterKey,
|
|
21289
22791
|
auditLog,
|
|
21290
22792
|
policy,
|
|
21291
|
-
keyProtection
|
|
22793
|
+
keyProtection,
|
|
22794
|
+
reputationStore
|
|
21292
22795
|
});
|
|
21293
22796
|
const { tools: memoryAttestTools } = createMemoryAttestTools(
|
|
21294
22797
|
identityManager,
|
|
@@ -21474,6 +22977,7 @@ exports.ContextGatePolicyStore = ContextGatePolicyStore;
|
|
|
21474
22977
|
exports.DashboardApprovalChannel = DashboardApprovalChannel;
|
|
21475
22978
|
exports.FederationRegistry = FederationRegistry;
|
|
21476
22979
|
exports.FilesystemStorage = FilesystemStorage;
|
|
22980
|
+
exports.HERO_COPY = HERO_COPY;
|
|
21477
22981
|
exports.InMemoryModelProvenanceStore = InMemoryModelProvenanceStore;
|
|
21478
22982
|
exports.InjectionDetector = InjectionDetector;
|
|
21479
22983
|
exports.MODEL_PRESETS = MODEL_PRESETS;
|
|
@@ -21501,15 +23005,19 @@ exports.filterContext = filterContext;
|
|
|
21501
23005
|
exports.generateAttestation = generateAttestation;
|
|
21502
23006
|
exports.generateSHR = generateSHR;
|
|
21503
23007
|
exports.generateSystemPrompt = generateSystemPrompt;
|
|
23008
|
+
exports.getProtectionSnapshot = getProtectionSnapshot;
|
|
21504
23009
|
exports.getTemplate = getTemplate;
|
|
21505
23010
|
exports.initiateHandshake = initiateHandshake;
|
|
21506
23011
|
exports.listTemplateIds = listTemplateIds;
|
|
21507
23012
|
exports.loadConfig = loadConfig;
|
|
21508
23013
|
exports.loadPrincipalPolicy = loadPrincipalPolicy;
|
|
21509
23014
|
exports.recommendPolicy = recommendPolicy;
|
|
23015
|
+
exports.renderDashboardHTML = renderDashboardHTML;
|
|
21510
23016
|
exports.resolveTier = resolveTier;
|
|
21511
23017
|
exports.respondToHandshake = respondToHandshake;
|
|
21512
23018
|
exports.signPayload = signPayload;
|
|
23019
|
+
exports.startDashboard = startDashboard;
|
|
23020
|
+
exports.startDashboardServer = startDashboardServer;
|
|
21513
23021
|
exports.tierDistribution = tierDistribution;
|
|
21514
23022
|
exports.verifyAttestation = verifyAttestation;
|
|
21515
23023
|
exports.verifyBridgeCommitment = verifyBridgeCommitment;
|