@wenathlan/extension 1.1.60 → 1.1.61

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.
@@ -3,5 +3,5 @@
3
3
  <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Devthink review</title><link rel="stylesheet" href="style.css"></head>
4
4
  <body><main class="wide"><header><p class="eyebrow">REVIEW GATE</p><h1>Plan before action</h1><p>Every browser operation remains blocked until the user reviews and approves it here.</p></header><label for="objective">Objective</label><textarea id="objective" rows="4" placeholder="Describe a browser task to turn into a reviewable plan."></textarea><div class="actions"><button id="localplan">Create local observation plan</button><button id="remoteplan" class="secondary">Ask configured endpoint</button><button id="diagnostic" class="secondary">Capture local diagnostics</button></div>
5
5
  <div class="actions"><input id="command" placeholder="Ask any connected model in plain language" aria-label="Natural language command"><button id="commandsend" class="secondary">Ask model</button><span id="intentbadge" class="muted"></span></div><p id="status" role="status">Loading session state.</p><p id="capabilitiestext" role="status" class="muted">Optional capabilities unknown.</p><section><h2>Current plan</h2><progress id="planprogress" max="1" value="0"></progress><div id="plan" class="panel"></div></section><section><h2>Clickable map</h2><ol id="map" class="audit maplist"></ol></section><section><h2>Navigation trail</h2><ol id="trail" class="audit"></ol></section><section><h2>Navigation state</h2><div id="navigation" class="panel"></div></section><section><h2>Tabs and windows</h2><div id="tabswindows" class="panel"></div></section><section><h2>Forms and data</h2><div id="forms" class="panel"></div></section><section><h2>Files and downloads</h2><div id="files" class="panel"></div></section><section><h2>Capture gallery</h2><div id="captures" class="panel"></div></section><section><h2>Media</h2><div id="media" class="panel"></div></section><section><h2>Network calls</h2><div id="calls" class="panel"></div></section><section><h2>Network view</h2><div id="netview" class="panel"></div></section><section><h2>Traffic control</h2><div id="traffic" class="panel"></div></section><section><h2>Run timeline</h2><div id="timeline" class="panel"></div></section><section><h2>Debugger</h2><div id="debugger" class="panel"></div></section><section><h2>Profiling</h2><div id="profiling" class="panel"></div></section><section><h2>Emulation</h2><div id="emulation" class="panel"></div></section><section><h2>Sessions</h2><div id="sessions" class="panel"></div></section><section><h2>Workflows</h2><div id="workflows" class="panel"></div></section><section><h2>Workflow editor</h2><div id="workfloweditor" class="panel"></div></section><section><h2>Triggers</h2><div id="triggers" class="panel"></div></section><section><h2>Agent protocol</h2><div id="agentprotocol" class="panel"></div></section>
6
- <section><h2>Models</h2><div id="models" class="panel"></div></section><section><h2>Agents</h2><div id="agents" class="panel"></div></section><section><h2>Execution environments</h2><div id="environments" class="panel"></div></section><section><h2>Console diff</h2><div id="consolediff" class="panel"></div></section><section><h2>Datasets</h2><div id="datasets" class="panel"></div></section><section><h2>Consent banners</h2><div id="banners" class="panel"></div></section><section><h2>Accessibility tree</h2><div id="a11y" class="panel"></div></section><section><h2>Reader view</h2><div id="reader" class="panel"></div></section><section><h2>Detected shapes</h2><ol id="detections" class="audit maplist"></ol></section><section><h2>Watch stream</h2><ol id="stream" class="audit"></ol></section><section><h2>Snapshot diffs</h2><div id="diffs" class="panel"></div></section><section><h2>Derived selectors</h2><ol id="selectors" class="audit maplist"></ol></section><section><h2>Structured diagnostics</h2><ul id="diagnostics" class="audit"></ul></section><section><h2>Local audit</h2><ol id="audit" class="audit"></ol></section></main><script type="module" src="sidepanel.js"></script></body>
6
+ <section><h2>Models</h2><div id="models" class="panel"></div></section><section><h2>Agents</h2><div id="agents" class="panel"></div></section><section><h2>Execution environments</h2><div id="environments" class="panel"></div></section><section><h2>Security</h2><div id="security" class="panel"></div></section><section><h2>Console diff</h2><div id="consolediff" class="panel"></div></section><section><h2>Datasets</h2><div id="datasets" class="panel"></div></section><section><h2>Consent banners</h2><div id="banners" class="panel"></div></section><section><h2>Accessibility tree</h2><div id="a11y" class="panel"></div></section><section><h2>Reader view</h2><div id="reader" class="panel"></div></section><section><h2>Detected shapes</h2><ol id="detections" class="audit maplist"></ol></section><section><h2>Watch stream</h2><ol id="stream" class="audit"></ol></section><section><h2>Snapshot diffs</h2><div id="diffs" class="panel"></div></section><section><h2>Derived selectors</h2><ol id="selectors" class="audit maplist"></ol></section><section><h2>Structured diagnostics</h2><ul id="diagnostics" class="audit"></ul></section><section><h2>Local audit</h2><ol id="audit" class="audit"></ol></section></main><script type="module" src="sidepanel.js"></script></body>
7
7
  </html>
@@ -71,6 +71,51 @@ function regexruleof(value) {
71
71
  return { pattern: candidate.pattern, flags: candidate.flags, groups };
72
72
  }
73
73
 
74
+ // originpolicy.ts
75
+ function stepoptions(step) {
76
+ if (!step.options) return {};
77
+ try {
78
+ const parsed = JSON.parse(step.options);
79
+ return Boolean(parsed) && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
80
+ } catch {
81
+ return {};
82
+ }
83
+ }
84
+ var paymentkinds = /* @__PURE__ */ new Set(["fillcard", "fillcode"]);
85
+ var credentialkinds = /* @__PURE__ */ new Set(["consentpassword", "saveapikey", "handleauth", "authflow"]);
86
+ var deletekinds = /* @__PURE__ */ new Set(["discardtab", "closepattern", "clearcookies", "removeattribute", "cleanupartifacts"]);
87
+ var publishkinds = /* @__PURE__ */ new Set(["postform", "postfiles", "sendmessage", "submitform", "submitsearch", "writeclipboard"]);
88
+ var defaultsensitivekinds = /* @__PURE__ */ new Set(["attachfile", "uploadfile", "uploadfiles", "downloadfile", "downloadimages", "batchdownload", "pausedownload", "resumedownload", "quarantinedownload", "evaluate"]);
89
+ function sensitiveclassesof(step) {
90
+ const options2 = stepoptions(step);
91
+ const fields = Array.isArray(options2.fields) ? options2.fields.filter((item) => Boolean(item) && typeof item === "object") : [];
92
+ const names = [...fields.map((field) => typeof field.name === "string" ? field.name : ""), typeof options2.field === "string" ? options2.field : "", typeof options2.target === "string" ? options2.target : ""].map((name) => name.toLowerCase());
93
+ const carries = (shape) => names.some((name) => name.includes(shape));
94
+ const classes = /* @__PURE__ */ new Set();
95
+ if (paymentkinds.has(step.kind) || carries("card") || carries("cvc") || carries("cvv")) classes.add("payment");
96
+ const credentialshape = carries("password") || carries("token") || carries("secret") || carries("apikey") || carries("passphrase");
97
+ const submits = step.kind === "submitform" || step.kind === "postform" || step.kind === "submitsearch" || step.kind === "fillform" || step.kind === "filllabel" || step.kind === "fillplaceholder";
98
+ if (credentialkinds.has(step.kind) || submits && credentialshape) classes.add("credential");
99
+ if (deletekinds.has(step.kind)) classes.add("delete");
100
+ if (publishkinds.has(step.kind) || step.kind === "callrest" || step.kind === "callgraphql") {
101
+ const verb = typeof options2.method === "string" ? options2.method.trim().toUpperCase() : typeof options2.verb === "string" ? options2.verb.trim().toUpperCase() : "";
102
+ if (step.kind === "callrest" || step.kind === "callgraphql") {
103
+ if (verb !== "" && !["GET", "HEAD", "OPTIONS"].includes(verb)) classes.add("publish");
104
+ } else classes.add("publish");
105
+ }
106
+ const bydefault = defaultsensitivekinds.has(step.kind);
107
+ const list = [...classes];
108
+ if (list.length === 0 && !bydefault) return { classes: [], bydefault: false, sensitive: false, reason: `The ${step.kind} kind carries no sensitive class and no default sensitive grade.` };
109
+ return { classes: list, bydefault, sensitive: true, reason: `The ${step.kind} kind grades sensitive${list.length > 0 ? ` through the ${list.join(", ")} class${list.length === 1 ? "" : "es"}` : ""}${bydefault ? " by default" : ""}.` };
110
+ }
111
+ function denydefaultnotice(origin) {
112
+ return `The denydefault posture refuses ${origin} until the user adds the origin to the automation allowlist; no step dispatches without the grant.`;
113
+ }
114
+ function profilesummary(profile) {
115
+ if (profile === void 0) return "No origin profile exists for this origin yet; sensitive steps route through their fresh consent prompts.";
116
+ return `The origin profile of ${profile.origin} grants ${profile.grants.length} kind${profile.grants.length === 1 ? "" : "s"} and denies ${profile.denials.length} kind${profile.denials.length === 1 ? "" : "s"} the user reviewed.`;
117
+ }
118
+
74
119
  // policy.ts
75
120
  var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select", "presskey", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "reload", "back", "forward", "writestorage", "setattribute", "removeattribute", "evaluate", "tabcreate", "tabactivate", "tabclose", "tabreload", "windowcreate", "windowclose", "windowresize", "downloadfile", "clickpoint", "shiftclick", "dismissdialog", "enterframe", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "openlink", "openprivate", "reloadcache", "stopnav", "followlink", "spanav", "rewritequery", "setfragment", "navlist", "navprofile", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "pausenav", "navrate", "openclipboard", "batchopen", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "restorelayout", "reopenrun", "badgetab", "fillform", "filllabel", "fillplaceholder", "submitform", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcard", "fillcode", "consentpassword", "exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk", "paginateextract", "resumeextract", "batchdownload", "pausedownload", "resumedownload", "interceptmime", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "cleanupartifacts", "recordscreen", "captureaudio", "downloadimages", "callrest", "callgraphql", "sendmessage", "blockrequest", "mockresponse", "rewriteheaders", "setcookies", "clearcookies", "authflow", "saveapikey", "routeproxy", "postform", "postfiles", "attachcdp", "detachcdp", "cdpcmd", "overridescript", "heapshot", "profilecpu", "capturesourcemaps", "emulatedevice", "emulatenetwork", "emulatelocate", "setuseragent", "overridepermission", "restoresession", "exportsessions", "importsessions", "runworkflow", "visitrule", "urlrule", "menurule", "keyrule", "buttonrule", "cronrule", "intervalrule", "urllistrule", "webhookrule", "eventrule"]);
76
121
  var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction", "capturebodies", "setbreakpoint", "stepcode", "watchexpr", "loop", "repeatuntil", "whileloop", "foreach", "parallel", "trycatch"]);
@@ -402,6 +447,7 @@ var agentprotocolroot = document.querySelector("#agentprotocol");
402
447
  var modelsroot = document.querySelector("#models");
403
448
  var agentsroot = document.querySelector("#agents");
404
449
  var environmentsroot = document.querySelector("#environments");
450
+ var securityroot = document.querySelector("#security");
405
451
  var commandinput = document.querySelector("#command");
406
452
  var commandbutton = document.querySelector("#commandsend");
407
453
  var intentbadge = document.querySelector("#intentbadge");
@@ -2898,6 +2944,7 @@ async function refresh() {
2898
2944
  rendermodels(context);
2899
2945
  renderagents(context);
2900
2946
  renderenvironments(context);
2947
+ rendersecurity(context);
2901
2948
  renderconsolediff();
2902
2949
  renderaudit(context.audit);
2903
2950
  rendercapabilities(context.capabilities);
@@ -5558,6 +5605,238 @@ function rendermodels(context) {
5558
5605
  }
5559
5606
  modelsroot.append(guardbox);
5560
5607
  }
5608
+ function rendersecurity(context) {
5609
+ if (!securityroot) return;
5610
+ securityroot.replaceChildren();
5611
+ const view = context.security;
5612
+ if (!view) {
5613
+ securityroot.textContent = "The security state is unknown.";
5614
+ return;
5615
+ }
5616
+ const head = document.createElement("p");
5617
+ head.textContent = `Posture ${view.posture}: the allowlist holds ${view.allowlist.length} exact origin${view.allowlist.length === 1 ? "" : "s"} with no wildcard expansion, ${view.profiles.length} origin profile${view.profiles.length === 1 ? "" : "s"}, ${view.windows.filter((window2) => window2.state === "active").length} active consent window${view.windows.filter((window2) => window2.state === "active").length === 1 ? "" : "s"} and ${view.revocations.length} recorded revocation${view.revocations.length === 1 ? "" : "s"}.`;
5618
+ securityroot.append(head);
5619
+ const allowbox = document.createElement("details");
5620
+ allowbox.className = "sessiongroup";
5621
+ const allowsummary = document.createElement("summary");
5622
+ allowsummary.textContent = "Automation allowlist (denydefault)";
5623
+ allowbox.append(allowsummary);
5624
+ if (view.allowlist.length === 0) {
5625
+ const none = document.createElement("p");
5626
+ none.textContent = "No origin is granted yet; the denydefault posture refuses every ungranted origin.";
5627
+ allowbox.append(none);
5628
+ }
5629
+ for (const entry of view.allowlist) {
5630
+ const line = document.createElement("p");
5631
+ line.textContent = `${entry.origin} \xB7 profile workspace ${entry.profileid} \xB7 granted ${new Date(entry.grantedat).toLocaleString()}`;
5632
+ line.append(" ", button("Remove", async () => {
5633
+ await request({ kind: "security", allowlist: { remove: { origin: entry.origin } } });
5634
+ await refresh();
5635
+ }));
5636
+ allowbox.append(line);
5637
+ }
5638
+ const origininput = document.createElement("input");
5639
+ origininput.placeholder = "https://exact.example";
5640
+ origininput.setAttribute("aria-label", "Exact origin to grant");
5641
+ const addorigin = button("Grant exact origin", async () => {
5642
+ const origin = origininput.value.trim();
5643
+ if (origin === "") throw new Error("The allowlist grant needs its exact origin.");
5644
+ await request({ kind: "security", allowlist: { add: { origin } } });
5645
+ origininput.value = "";
5646
+ await refresh();
5647
+ });
5648
+ allowbox.append(origininput, " ", addorigin);
5649
+ const allownote = document.createElement("p");
5650
+ allownote.className = "muted";
5651
+ allownote.textContent = "The allowlist binds every grant to one exact origin with no wildcard expansion; the active tab grant counts as exactly one explicit single origin grant, and grants never outlive their named boundary.";
5652
+ allowbox.append(allownote);
5653
+ securityroot.append(allowbox);
5654
+ const profilebox = document.createElement("details");
5655
+ profilebox.className = "sessiongroup";
5656
+ const profilesummarynode = document.createElement("summary");
5657
+ profilesummarynode.textContent = "Origin profiles";
5658
+ profilebox.append(profilesummarynode);
5659
+ if (view.profiles.length === 0) {
5660
+ const none = document.createElement("p");
5661
+ none.textContent = profilesummary(void 0);
5662
+ profilebox.append(none);
5663
+ }
5664
+ for (const profile of view.profiles) {
5665
+ const line = document.createElement("p");
5666
+ line.textContent = `${profile.origin}: ${profile.grants.length} granted kind${profile.grants.length === 1 ? "" : "s"}${profile.denials.length > 0 ? `, ${profile.denials.length} denied kind${profile.denials.length === 1 ? "" : "s"}` : ""}.`;
5667
+ profilebox.append(line);
5668
+ }
5669
+ if (context.session?.origin !== void 0) {
5670
+ const kindinput = document.createElement("input");
5671
+ kindinput.placeholder = "action kind such as submitform";
5672
+ kindinput.setAttribute("aria-label", "Action kind for the origin profile");
5673
+ profilebox.append(kindinput, " ", button("Grant kind on active origin", async () => {
5674
+ const kind = kindinput.value.trim();
5675
+ if (kind === "") throw new Error("The origin profile decision needs its action kind.");
5676
+ await request({ kind: "security", profile: { origin: context.session?.origin, kind, decision: "grant" } });
5677
+ await refresh();
5678
+ }), " ", button("Deny kind on active origin", async () => {
5679
+ const kind = kindinput.value.trim();
5680
+ if (kind === "") throw new Error("The origin profile decision needs its action kind.");
5681
+ await request({ kind: "security", profile: { origin: context.session?.origin, kind, decision: "deny" } });
5682
+ await refresh();
5683
+ }));
5684
+ }
5685
+ securityroot.append(profilebox);
5686
+ const windowbox = document.createElement("details");
5687
+ windowbox.className = "sessiongroup";
5688
+ const windowsummary = document.createElement("summary");
5689
+ windowsummary.textContent = "Consent windows";
5690
+ windowbox.append(windowsummary);
5691
+ const now = Date.now();
5692
+ if (view.windows.length === 0) {
5693
+ const none = document.createElement("p");
5694
+ none.textContent = "No consent window exists yet; every sensitive step waits for its prompt.";
5695
+ windowbox.append(none);
5696
+ }
5697
+ for (const window2 of view.windows) {
5698
+ const remaining = window2.state === "active" ? Math.max(0, window2.expiresat - now) : 0;
5699
+ const line = document.createElement("p");
5700
+ line.textContent = `${window2.origin} \xB7 ${window2.state === "active" ? `active, ${Math.ceil(remaining / 1e3)}s of the ${window2.boundary} boundary left` : `closed at ${new Date(window2.closedat ?? window2.expiresat).toLocaleString()}`} \xB7 ${window2.kinds.length} kind${window2.kinds.length === 1 ? "" : "s"} covered.`;
5701
+ if (window2.state === "active") {
5702
+ const durationinput2 = document.createElement("input");
5703
+ durationinput2.placeholder = "duration in ms";
5704
+ durationinput2.setAttribute("aria-label", "Renewal duration in milliseconds");
5705
+ line.append(" ", durationinput2, " ", button("Renew", async () => {
5706
+ const duration = Number(durationinput2.value.trim());
5707
+ const offer = Number.isFinite(duration) && duration > 0 ? duration : view.promptduration;
5708
+ if (offer === void 0) throw new Error("The renewal prompt needs its duration in milliseconds; no grant ever defaults to unlimited.");
5709
+ await request({ kind: "security", consent: { renew: { windowid: window2.id, duration: offer } } });
5710
+ await refresh();
5711
+ }));
5712
+ }
5713
+ windowbox.append(line);
5714
+ }
5715
+ const durationinput = document.createElement("input");
5716
+ durationinput.placeholder = "duration in ms";
5717
+ durationinput.setAttribute("aria-label", "Consent window duration in milliseconds");
5718
+ const offered = view.promptduration !== void 0 ? String(view.promptduration) : "";
5719
+ if (offered !== "") durationinput.value = offered;
5720
+ windowbox.append(durationinput, " ", button("Open consent window", async () => {
5721
+ const duration = Number(durationinput.value.trim());
5722
+ if (!Number.isFinite(duration) || duration <= 0) throw new Error("The consent prompt needs its duration in milliseconds; no grant ever defaults to unlimited.");
5723
+ await request({ kind: "security", consent: { open: { duration } } });
5724
+ await refresh();
5725
+ }));
5726
+ const windownote = document.createElement("p");
5727
+ windownote.className = "muted";
5728
+ windownote.textContent = "Every consent window scopes to one session and one origin and names its boundary; a window past its boundary suspends the run mid step and the executor refuses to resume without a new explicit prompt.";
5729
+ windowbox.append(windownote);
5730
+ securityroot.append(windowbox);
5731
+ const promptbox = document.createElement("details");
5732
+ promptbox.className = "sessiongroup";
5733
+ const promptsummary = document.createElement("summary");
5734
+ promptsummary.textContent = "Consent prompts of the pending plan";
5735
+ promptbox.append(promptsummary);
5736
+ const prompts = view.prompts ?? [];
5737
+ if (prompts.length === 0) {
5738
+ const none = document.createElement("p");
5739
+ none.textContent = "No sensitive step of the current plan waits for a fresh consent prompt.";
5740
+ promptbox.append(none);
5741
+ }
5742
+ for (const pending of prompts) {
5743
+ const classification = context.plan?.steps.find((step) => step.id === pending.stepid) !== void 0 ? sensitiveclassesof(context.plan.steps.find((step) => step.id === pending.stepid)) : { classes: [], bydefault: false };
5744
+ const line = document.createElement("p");
5745
+ line.textContent = `${pending.prompt}${classification.classes.length > 0 ? ` Class badge: ${classification.classes.join(", ")}.` : classification.bydefault ? " Class badge: sensitive by default." : ""}`;
5746
+ promptbox.append(line);
5747
+ }
5748
+ if (context.session?.origin !== void 0 && prompts.length > 0) {
5749
+ promptbox.append(button("Consent to every sensitive class", async () => {
5750
+ const classes = /* @__PURE__ */ new Set();
5751
+ for (const pending of prompts) for (const step of context.plan?.steps ?? []) if (step.id === pending.stepid) for (const classname of sensitiveclassesof(step).classes) classes.add(classname);
5752
+ if (classes.size === 0) throw new Error("The pending steps grade sensitive by default; open their consent window instead.");
5753
+ await request({ kind: "security", consent: { classes: { origin: context.session?.origin, classes: [...classes] } } });
5754
+ await refresh();
5755
+ }));
5756
+ }
5757
+ securityroot.append(promptbox);
5758
+ const consentbox = document.createElement("div");
5759
+ consentbox.className = "actions";
5760
+ const revoke = button("Revoke consent mid run", async () => {
5761
+ const plan = context.plan;
5762
+ if (!plan) throw new Error("The revocation needs its run; no plan is open.");
5763
+ const halted = plan.steps.map((step) => `${step.id} (${step.kind})`);
5764
+ const confirmnode = document.createElement("p");
5765
+ confirmnode.textContent = `Confirm the revokerun: the pending step and every queued step halt without executing${halted.length > 0 ? ` (${halted.length} step${halted.length === 1 ? "" : "s"} of the plan: ${halted.slice(0, 5).join(", ")}${halted.length > 5 ? ", \u2026" : ""})` : ""}.`;
5766
+ confirmnode.append(" ", button("Confirm revokerun", async () => {
5767
+ await request({ kind: "security", revoke: { runid: plan.id } });
5768
+ await refresh();
5769
+ }), " ", button("Cancel", async () => {
5770
+ confirmnode.remove();
5771
+ }));
5772
+ securityroot.prepend(confirmnode);
5773
+ });
5774
+ consentbox.append(revoke);
5775
+ const revocations = view.revocations;
5776
+ if (revocations.length > 0) consentbox.append(document.createElement("span"), document.createTextNode(`Last revocation: ${revocations[0]?.runid} halted ${revocations[0]?.haltedstepids.length} step${revocations[0]?.haltedstepids.length === 1 ? "" : "s"}.`));
5777
+ securityroot.append(consentbox);
5778
+ const maskbox = document.createElement("details");
5779
+ maskbox.className = "sessiongroup";
5780
+ const masksummary = document.createElement("summary");
5781
+ masksummary.textContent = "Mask rules for sensitive field shapes";
5782
+ maskbox.append(masksummary);
5783
+ const shapeinput = document.createElement("input");
5784
+ shapeinput.placeholder = "field shape such as accountnumber";
5785
+ shapeinput.setAttribute("aria-label", "Field shape to mask");
5786
+ maskbox.append(shapeinput, " ", button("Add mask shape", async () => {
5787
+ const shape = shapeinput.value.trim();
5788
+ if (shape === "") throw new Error("The mask rule needs its field shape.");
5789
+ await request({ kind: "security", mask: { add: { shapes: [shape] } } });
5790
+ await refresh();
5791
+ }));
5792
+ if (view.maskrules.length === 0) {
5793
+ const none = document.createElement("p");
5794
+ none.textContent = "No custom mask rule exists; the documented password, token, card and secret shapes always apply.";
5795
+ maskbox.append(none);
5796
+ }
5797
+ for (const rule of view.maskrules) {
5798
+ const line = document.createElement("p");
5799
+ line.textContent = `${rule.shapes.join(", ")}${rule.origin !== void 0 ? ` scoped to ${rule.origin}` : " global"} \xB7 values show as [redacted] fields in step details.`;
5800
+ line.append(" ", button("Remove", async () => {
5801
+ await request({ kind: "security", mask: { remove: { id: rule.id } } });
5802
+ await refresh();
5803
+ }));
5804
+ maskbox.append(line);
5805
+ }
5806
+ securityroot.append(maskbox);
5807
+ const chainbox = document.createElement("details");
5808
+ chainbox.className = "sessiongroup";
5809
+ const chainsummary = document.createElement("summary");
5810
+ chainsummary.textContent = "Run log chain verification";
5811
+ chainbox.append(chainsummary);
5812
+ if (view.chain.length === 0) {
5813
+ const none = document.createElement("p");
5814
+ none.textContent = "No run log exists yet; the session start writes the consentscope grant into the immutable log.";
5815
+ chainbox.append(none);
5816
+ }
5817
+ for (const chain of view.chain) {
5818
+ const line = document.createElement("p");
5819
+ line.textContent = `${chain.runid}: ${chain.valid ? "chain verified" : `chain broken at entry ${chain.brokenat ?? "unknown"}`} \xB7 ${chain.entries} entries${chain.sealhash !== void 0 ? ` \xB7 seal hash ${chain.sealhash.slice(0, 12)}\u2026 at ${new Date(chain.sealedat ?? 0).toLocaleString()}` : " \xB7 unsealed"}.`;
5820
+ line.append(" ", button("Read log", async () => {
5821
+ const read = await request({ kind: "security", read: { runid: chain.runid } });
5822
+ status(read.verification ?? `The log of ${chain.runid} read through the audit accessor.`);
5823
+ }), " ", button("Export audit file", async () => {
5824
+ const exported = await request({ kind: "security", export: { runid: chain.runid } });
5825
+ status(`The verified run log of ${chain.runid} was exported as an audit file with ${exported.export?.entries ?? 0} entries.`);
5826
+ }));
5827
+ chainbox.append(line);
5828
+ }
5829
+ const chainnote = document.createElement("p");
5830
+ chainnote.className = "muted";
5831
+ chainnote.textContent = "The immutable log appends only: every entry chains through its loghash to its predecessor, the session close seals the chain with a final hash, and the audit accessor refuses reads of a chain with a broken link.";
5832
+ chainbox.append(chainnote);
5833
+ securityroot.append(chainbox);
5834
+ if (view.sessionorigin !== void 0 && !view.allowlist.some((entry) => entry.origin === view.sessionorigin)) {
5835
+ const notice = document.createElement("p");
5836
+ notice.textContent = denydefaultnotice(view.sessionorigin);
5837
+ securityroot.append(notice);
5838
+ }
5839
+ }
5561
5840
  function renderenvironments(context) {
5562
5841
  if (!environmentsroot) return;
5563
5842
  environmentsroot.replaceChildren();