function ckpApi() {
return window.LctxApi && window.LctxApi.apiFetch ? window.LctxApi.apiFetch : null;
}
function ckpEsc(s) {
if (window.LctxFmt && typeof window.LctxFmt.esc === 'function') {
return window.LctxFmt.esc(String(s == null ? '' : s));
}
var d = document.createElement('div');
d.textContent = String(s == null ? '' : s);
return d.innerHTML;
}
var CKP_COVERAGE_META = {
Full: { cls: 'ok', tip: 'All listed mitigations ship enabled by default.' },
Partial: { cls: 'warn', tip: 'Mitigations reduce but do not eliminate this risk.' },
Minimal: { cls: 'dim', tip: 'Only indirect coverage — pair with external controls.' },
};
class CockpitProtection extends HTMLElement {
constructor() {
super();
this._loading = true;
this._error = null;
this._risk = null;
this._owasp = null;
this._onRefresh = this._onRefresh.bind(this);
}
connectedCallback() {
if (this._ready) return;
this._ready = true;
this.style.display = 'block';
document.addEventListener('lctx:refresh', this._onRefresh);
this.render();
}
disconnectedCallback() {
document.removeEventListener('lctx:refresh', this._onRefresh);
}
_onRefresh() {
var v = document.getElementById('view-protection');
if (v && v.classList.contains('active')) this.loadData();
}
async loadData() {
var fetchJson = ckpApi();
if (!fetchJson) {
this._error = 'API client not loaded';
this._loading = false;
this.render();
return;
}
this._loading = true;
this._error = null;
this.render();
var results = await Promise.all([
fetchJson('/api/context-risk', { timeoutMs: 10000 }).catch(function (e) {
return { __error: e && e.error ? e.error : String(e || 'error') };
}),
fetchJson('/api/owasp', { timeoutMs: 10000 }).catch(function (e) {
return { __error: e && e.error ? e.error : String(e || 'error') };
}),
]);
this._risk = results[0] && !results[0].__error ? results[0] : null;
this._owasp = Array.isArray(results[1]) ? results[1] : null;
if (!this._risk && !this._owasp) {
this._error = 'Could not load protection data';
}
this._loading = false;
this.render();
}
render() {
if (this._loading) {
this.innerHTML =
'<div class="card"><div class="empty-state">Loading protection data…</div></div>';
return;
}
if (this._error) {
this.innerHTML =
'<div class="card"><div class="empty-state">' + ckpEsc(this._error) + '</div></div>';
return;
}
this.innerHTML =
this._renderRiskSummary() +
this._renderWarnings() +
this._renderOwasp();
}
_renderRiskSummary() {
var r = this._risk;
if (!r) return '';
var h = r.compression_health || {};
var o = r.overlay_summary || {};
var warnings = Array.isArray(r.warnings) ? r.warnings : [];
var warnCls = warnings.length ? 'warn' : 'ok';
return (
'<div class="card">' +
'<div class="card-title" title="Live risk picture of the current session: are agents editing files they only saw compressed, and which overlays steer what they read?">Session risk</div>' +
'<div class="prot-kpis">' +
'<div class="prot-kpi ' + warnCls + '"><div class="prot-kpi-v">' + warnings.length + '</div>' +
'<div class="prot-kpi-l">open warnings</div></div>' +
'<div class="prot-kpi"><div class="prot-kpi-v">' + (h.files_read_full || 0) + '</div>' +
'<div class="prot-kpi-l">files read full</div></div>' +
'<div class="prot-kpi"><div class="prot-kpi-v">' + (h.files_read_compressed || 0) + '</div>' +
'<div class="prot-kpi-l">files read compressed</div></div>' +
'<div class="prot-kpi' + (h.files_edited_after_compressed ? ' warn' : '') + '">' +
'<div class="prot-kpi-v">' + (h.files_edited_after_compressed || 0) + '</div>' +
'<div class="prot-kpi-l">edited after compressed read</div></div>' +
'<div class="prot-kpi"><div class="prot-kpi-v">' + (o.pinned || 0) + ' / ' + (o.excluded || 0) + '</div>' +
'<div class="prot-kpi-l">overlays pinned / excluded</div></div>' +
'</div></div>'
);
}
_renderWarnings() {
var r = this._risk;
if (!r) return '';
var warnings = Array.isArray(r.warnings) ? r.warnings : [];
var body;
if (!warnings.length) {
body =
'<div class="empty-state">No risk warnings — no file was edited after a compressed-only read in this session.</div>';
} else {
body = warnings
.map(function (w) {
return (
'<div class="prot-warning sev-' + ckpEsc(w.severity || 'low') + '">' +
'<div class="prot-warning-head">' +
'<span class="tag tr">' + ckpEsc(w.severity || '') + '</span>' +
'<code>' + ckpEsc(w.path || '') + '</code>' +
(w.mode ? '<span class="tag tg">' + ckpEsc(w.mode) + '</span>' : '') +
'</div>' +
'<div class="prot-warning-msg">' + ckpEsc(w.message || '') + '</div>' +
(w.suggestion
? '<div class="prot-warning-fix">' + ckpEsc(w.suggestion) + '</div>'
: '') +
'</div>'
);
})
.join('');
}
return (
'<div class="card">' +
'<div class="card-title" title="Raised when a file is edited although it was only read in a compressed mode — the one compression risk worth watching.">Risk warnings</div>' +
body +
'</div>'
);
}
_renderOwasp() {
var rows = this._owasp;
if (!rows || !rows.length) return '';
var cards = rows
.map(function (m) {
var meta = CKP_COVERAGE_META[m.coverage] || CKP_COVERAGE_META.Minimal;
var mitigations = (m.lean_ctx_mitigations || [])
.map(function (mit) {
var tipTxt = (mit.module || '') + ' — ' + (mit.description || '');
return (
'<span class="prot-mitigation" title="' + ckpEsc(tipTxt) + '">' +
ckpEsc(mit.feature || '') + '</span>'
);
})
.join('');
return (
'<div class="prot-owasp-card">' +
'<div class="prot-owasp-head">' +
'<span class="prot-owasp-id">' + ckpEsc(m.owasp_id || '') + '</span>' +
'<span class="prot-coverage ' + meta.cls + '" title="' + ckpEsc(meta.tip) + '">' +
ckpEsc(m.coverage || '') + '</span>' +
'</div>' +
'<div class="prot-owasp-title">' + ckpEsc(m.owasp_title || '') + '</div>' +
'<div class="prot-owasp-risk">' + ckpEsc(m.risk_description || '') + '</div>' +
'<div class="prot-mitigations">' + mitigations + '</div>' +
'</div>'
);
})
.join('');
return (
'<div class="card">' +
'<div class="card-title" title="The same mapping `lean-ctx audit` prints: every OWASP agentic risk next to the built-in guard that mitigates it. Hover a guard for its module.">' +
'OWASP agentic-risk coverage</div>' +
'<div class="prot-owasp-grid">' + cards + '</div>' +
'</div>'
);
}
}
customElements.define('cockpit-protection', CockpitProtection);
export { CockpitProtection };