@prooflane/inspector-beta 0.1.0-beta.5 → 0.1.0-beta.6

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.mjs CHANGED
@@ -14730,39 +14730,56 @@ var resultTone = (result) => {
14730
14730
  return "review";
14731
14731
  return "risk";
14732
14732
  };
14733
+ var isAgentControl = (control) => control.verification?.kind === "model" || /prompt|retrieval|rag|agent|jailbreak|exfiltration/i.test(control.category);
14733
14734
  function writeHostedCampaignGraph(outputPath, campaign, scan) {
14734
14735
  const absolute = resolve5(outputPath);
14735
14736
  mkdirSync2(dirname4(absolute), { recursive: true });
14736
- const controls = scan.controls.map((control, index) => {
14737
+ const controlCard = (control, index) => {
14737
14738
  const findings = (control.findings ?? []).map((finding) => `
14738
- <li><strong>${escapeHtml3(finding.title)}</strong><span>${escapeHtml3(finding.description)}</span></li>`).join("");
14739
- return `<article class="control ${resultTone(control.result)}">
14740
- <div class="step">${String(index + 1).padStart(2, "0")}</div>
14741
- <div class="body">
14742
- <div class="row"><h2>${escapeHtml3(control.title)}</h2><span class="badge">${escapeHtml3(control.result ?? control.executionState)}</span></div>
14739
+ <article class="finding"><div class="finding-title"><strong>${escapeHtml3(finding.title)}</strong>${finding.severity ? `<span class="severity">${escapeHtml3(finding.severity)}</span>` : ""}</div><p>${escapeHtml3(finding.description)}</p>${finding.impact ? `<p><b>Impact:</b> ${escapeHtml3(finding.impact)}</p>` : ""}${finding.remediation ? `<p><b>Remediation:</b> ${escapeHtml3(finding.remediation)}</p>` : ""}</article>`).join("");
14740
+ const tone = resultTone(control.result);
14741
+ const label = control.result === "resisted" ? "BLOCKED" : String(control.result ?? control.executionState).replaceAll("_", " ").toUpperCase();
14742
+ return `<details class="control ${tone}" ${tone === "risk" ? "open" : ""}>
14743
+ <summary><span class="control-copy"><span class="category">${escapeHtml3(control.category)}</span><strong>${escapeHtml3(control.title)}</strong></span><span class="badge">${escapeHtml3(label)}</span></summary>
14744
+ <div class="control-detail">
14743
14745
  <p>${escapeHtml3(control.shortDescription)}</p>
14746
+ <div class="facts"><div><span>Category</span><b>${escapeHtml3(control.category)}</b></div><div><span>Severity</span><b>${escapeHtml3(control.severity ?? "info")}</b></div><div><span>Execution</span><b>${escapeHtml3(control.verification?.kind === "safe_exposure" ? "not performed" : control.executionState)}</b></div><div><span>Status</span><b>${escapeHtml3(control.result ?? control.executionState)}</b></div><div><span>Duration</span><b>${escapeHtml3(control.durationMs == null ? "\u2014" : `${control.durationMs} ms`)}</b></div><div><span>Graded by</span><b>${escapeHtml3(control.verification?.kind ?? "signed hosted control")}</b></div></div>
14747
+ <section class="verified"><span>How this was verified</span><p>${escapeHtml3(control.verification?.description ?? "Prooflane graded the compact evidence returned for this signed hosted control.")}</p></section>
14744
14748
  ${control.message ? `<p class="message">${escapeHtml3(control.message)}</p>` : ""}
14745
- ${findings ? `<ul>${findings}</ul>` : ""}
14749
+ ${findings ? `<section class="findings"><span>Capability findings</span>${findings}</section>` : ""}
14746
14750
  </div>
14747
- </article>`;
14748
- }).join('<div class="edge" aria-hidden="true"></div>');
14751
+ </details>`;
14752
+ };
14753
+ const graphNode = (control) => {
14754
+ const tone = resultTone(control.result);
14755
+ const label = control.result === "resisted" ? "BLOCKED" : String(control.result ?? control.executionState).replaceAll("_", " ").toUpperCase();
14756
+ return `<div class="node ${tone}"><span>${escapeHtml3(control.category)}</span><strong>${escapeHtml3(control.title)}</strong><b>${escapeHtml3(label)}</b></div>`;
14757
+ };
14758
+ const containment = scan.controls.filter((control) => !isAgentControl(control));
14759
+ const agent = scan.controls.filter(isAgentControl);
14760
+ const controls = scan.controls.map(controlCard).join("");
14761
+ const riskCount = scan.summary.exposed + scan.summary.breached + scan.summary.poisoned;
14762
+ const statusTone = scan.summary.breached > 0 ? "risk" : riskCount > 0 || scan.summary.inconclusive > 0 ? "review" : "safe";
14763
+ const statusCopy = scan.summary.breached > 0 ? `OBJECTIVE REACHED \xB7 ${campaign.objectiveLabel}` : riskCount > 0 ? `REVIEW REQUIRED \xB7 ${riskCount} exposed or poisoned control${riskCount === 1 ? "" : "s"}` : scan.summary.inconclusive > 0 ? "INCONCLUSIVE \xB7 no control verifiably failed" : "CONTAINED \xB7 every exercised control held";
14764
+ const tally = [`${scan.summary.breached} breached`, `${scan.summary.exposed} exposed`, `${scan.summary.inconclusive} inconclusive`, `${scan.summary.resisted} resisted`].join(" \xB7 ");
14749
14765
  const generatedAt = scan.completedAt ?? (/* @__PURE__ */ new Date()).toISOString();
14750
14766
  const html = `<!doctype html>
14751
14767
  <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
14752
14768
  <title>${escapeHtml3(campaign.name)} \xB7 Prooflane campaign report</title>
14753
14769
  <style>
14754
- :root{color-scheme:dark;--bg:#081016;--card:#111b24;--line:#2d4151;--text:#eaf0f5;--muted:#91a4b2;--gold:#f4aa2b;--green:#35d39a;--red:#ff6b6b;--amber:#f5c46b}*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 80% 0,#16313a 0,transparent 34%),var(--bg);color:var(--text);font:15px/1.55 Inter,ui-sans-serif,system-ui,-apple-system,sans-serif}.wrap{width:min(980px,calc(100% - 32px));margin:48px auto 80px}.eyebrow{color:var(--gold);font-weight:800;letter-spacing:.14em;text-transform:uppercase}h1{font-size:clamp(32px,6vw,64px);line-height:1.02;margin:12px 0 18px}p{color:var(--muted)}.meta{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin:28px 0}.metric{padding:18px;border:1px solid var(--line);border-radius:14px;background:#0d1720}.metric b{display:block;font-size:25px}.control{display:grid;grid-template-columns:58px 1fr;gap:18px;padding:22px;border:1px solid var(--line);border-radius:18px;background:var(--card)}.control.safe{border-color:#1f6b55}.control.risk{border-color:#833e43}.control.review{border-color:#725d31}.step{display:grid;place-items:center;width:48px;height:48px;border-radius:14px;border:1px solid var(--gold);color:var(--gold);font-weight:900}.row{display:flex;align-items:flex-start;justify-content:space-between;gap:18px}.row h2{font-size:19px;margin:0}.badge{border:1px solid currentColor;border-radius:999px;padding:4px 9px;font:700 11px/1.2 ui-monospace,monospace;text-transform:uppercase}.safe .badge{color:var(--green)}.risk .badge{color:var(--red)}.review .badge{color:var(--amber)}.control p{margin:8px 0 0}.message{color:#c9d4dc!important}.edge{height:22px;border-left:2px solid var(--line);margin-left:45px}ul{padding-left:20px}li span{display:block;color:var(--muted)}footer{margin-top:28px;padding-top:18px;border-top:1px solid var(--line);color:var(--muted)}@media(max-width:680px){.meta{grid-template-columns:1fr 1fr}.control{grid-template-columns:1fr}.edge{margin-left:23px}.row{display:block}.badge{display:inline-block;margin-top:8px}}
14770
+ :root{color-scheme:light;--bg:#f5f7f8;--card:#fff;--line:#d8e0e5;--text:#162028;--muted:#7b8d99;--gold:#bd630d;--green:#159b68;--green-bg:#e6f6ef;--red:#e04646;--red-bg:#fdeced;--amber:#b96a10;--amber-bg:#fff4d6}*{box-sizing:border-box}body{margin:0;background:linear-gradient(145deg,#fff 0,#f1f6f7 100%);color:var(--text);font:15px/1.55 Inter,ui-sans-serif,system-ui,-apple-system,sans-serif}.wrap{width:min(1180px,calc(100% - 32px));margin:32px auto 72px}.eyebrow,.category,.verified>span,.findings>span,.facts span{color:var(--muted);font:700 11px ui-monospace,monospace;letter-spacing:.08em;text-transform:uppercase}h1{font-size:28px;line-height:1.1;margin:8px 0 10px}p{color:var(--muted)}.status{margin:22px 0 12px;padding:18px 20px;border-radius:12px}.status strong{display:block}.status.safe{background:var(--green-bg);color:var(--green)}.status.risk{background:var(--red-bg);color:var(--red)}.status.review{background:var(--amber-bg);color:var(--amber)}.status span{color:var(--muted);font-size:13px}.attribution{padding:16px;border:1px solid var(--gold);border-radius:12px;background:#fffdf9}.attribution b{color:var(--gold);text-transform:uppercase;font-size:11px}.attribution p{margin:5px 0 0;color:var(--text)}.graph-group{margin-top:20px}.graph-group h2{font-size:14px;margin:0}.graph-group>p{font-size:13px;margin:3px 0 10px}.nodes{display:flex;gap:10px;align-items:stretch;flex-wrap:wrap}.node{display:grid;gap:4px;min-width:190px;max-width:280px;padding:14px;border:1px solid var(--line);border-radius:11px;background:var(--card)}.node span{color:var(--muted);font:700 10px ui-monospace,monospace;text-transform:uppercase}.node strong{font-size:13px}.node b{width:max-content;padding:2px 7px;border-radius:999px;font:700 10px ui-monospace,monospace}.node.safe{border-color:var(--green)}.node.safe b{color:var(--green);background:var(--green-bg)}.node.risk{border-color:var(--red)}.node.risk b{color:var(--red);background:var(--red-bg)}.node.review{border-color:var(--amber)}.node.review b{color:var(--amber);background:var(--amber-bg)}.objective{margin-top:20px;padding:14px;border:1px solid;border-radius:11px;font-weight:800}.objective.safe{border-color:var(--green);background:var(--green-bg);color:var(--green)}.objective.risk{border-color:var(--red);background:var(--red-bg);color:var(--red)}.details-title{margin:34px 0 12px}.control{margin-top:10px;border:1px solid var(--line);border-radius:12px;background:var(--card);overflow:hidden}.control.safe{border-color:var(--green)}.control.risk{border-color:var(--red)}.control.review{border-color:var(--amber)}summary{display:flex;justify-content:space-between;align-items:center;gap:18px;padding:16px;cursor:pointer;list-style:none}.control-copy{display:grid;gap:3px}.badge,.severity{border-radius:999px;padding:4px 9px;font:700 10px ui-monospace,monospace;text-transform:uppercase}.safe .badge{color:var(--green);background:var(--green-bg)}.risk .badge,.severity{color:var(--red);background:var(--red-bg)}.review .badge{color:var(--amber);background:var(--amber-bg)}.control-detail{padding:0 16px 18px;border-top:1px solid var(--line)}.facts{display:grid;grid-template-columns:repeat(3,minmax(140px,1fr));gap:14px;margin:14px 0;padding:14px;background:var(--bg);border-radius:10px}.facts div{display:grid;gap:3px}.verified,.findings{margin-top:14px}.verified p{margin:5px 0}.finding{margin-top:9px;padding:13px;border:1px solid var(--line);border-radius:9px}.finding-title{display:flex;justify-content:space-between;gap:10px}.finding p{margin:6px 0}.message{color:var(--text)!important}footer{margin-top:28px;padding-top:18px;border-top:1px solid var(--line);color:var(--muted);font-size:12px}@media(max-width:680px){.facts{grid-template-columns:1fr}.nodes{display:grid}.node{max-width:none}.row{display:block}}
14755
14771
  </style></head><body><main class="wrap">
14756
14772
  <div class="eyebrow">Prooflane \xB7 Hosted Security Campaign</div>
14757
14773
  <h1>${escapeHtml3(campaign.name)}</h1>
14758
14774
  <p>${escapeHtml3(campaign.objective)}</p>
14759
14775
  <p><strong>Outcome question:</strong> ${escapeHtml3(campaign.outcomeQuestion)}</p>
14760
- <section class="meta">
14761
- <div class="metric"><b>${scan.score ?? "\u2014"}</b><span>assurance score</span></div>
14762
- <div class="metric"><b>${scan.summary.resisted}</b><span>resisted</span></div>
14763
- <div class="metric"><b>${scan.summary.exposed + scan.summary.breached + scan.summary.poisoned}</b><span>risk findings</span></div>
14764
- <div class="metric"><b>${scan.summary.notExercised}</b><span>not exercised</span></div>
14765
- </section>
14776
+ <section class="status ${statusTone}"><strong>${escapeHtml3(statusCopy)}</strong><span>${escapeHtml3(tally)}</span></section>
14777
+ <p>Independent signed controls, grouped by what they test \u2014 not one execution. A held control never gates another.</p>
14778
+ ${agent.length ? `<section class="attribution"><b>Attribution</b><p>Agent-behavior findings test the configured model with a <strong>Prooflane-synthesized probe</strong>. They are not evidence that ${escapeHtml3(scan.target.name)} supplied malicious content or that a server boundary was crossed without corresponding MCP evidence.</p></section>` : ""}
14779
+ ${containment.length ? `<section class="graph-group"><h2>Containment controls</h2><p>Probed directly against ${escapeHtml3(scan.target.name)}. A reachable tool is exposed surface; only objective evidence is a verified breach.</p><div class="nodes">${containment.map(graphNode).join("")}</div></section>` : ""}
14780
+ ${agent.length ? `<section class="graph-group"><h2>Agent behavior \xB7 synthetic probe</h2><p>Measures the configured model/client under a Prooflane-authored scenario. A refusal is safe behavior; a failure is a model finding.</p><div class="nodes">${agent.map(graphNode).join("")}</div></section>` : ""}
14781
+ <section class="objective ${scan.summary.breached > 0 ? "risk" : "safe"}">${scan.summary.breached > 0 ? "\u2717" : "\u2713"} ${escapeHtml3(campaign.objectiveLabel)} \u2014 ${scan.summary.breached > 0 ? "REACHED" : "NOT REACHED"}</section>
14782
+ <h2 class="details-title">Detailed security report</h2>
14766
14783
  <section>${controls}</section>
14767
14784
  <footer>Scan ${escapeHtml3(scan.id)} \xB7 ${escapeHtml3(scan.target.name)} \xB7 ${escapeHtml3(scan.status)} \xB7 ${escapeHtml3(generatedAt)}<br>Report contains compact signed-control results only. Raw prompts, responses, retrieved data, and credentials remained on the local Runner.</footer>
14768
14785
  </main></body></html>`;