@occasiolabs/occasio 0.8.1 → 0.8.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@occasiolabs/occasio",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Occasio — cryptographically verifiable behavioral attestation for AI coding agents. Tool-call interception + policy enforcement + tamper-evident audit chain + Sigstore-signed in-toto attestations + windowed EDR detection. Same engine for Claude Code and MCP; Computer-Use scaffold included.",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -47,8 +47,8 @@
47
47
  "computer-use"
48
48
  ],
49
49
  "bin": {
50
- "occasio": "bin/occasio.js",
51
- "oc": "bin/occasio.js",
50
+ "occasio": "bin/occasio.js",
51
+ "oc": "bin/occasio.js",
52
52
  "occasio-mcp": "bin/occasio-mcp.js"
53
53
  },
54
54
  "engines": {
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "repository": {
58
58
  "type": "git",
59
- "url": "https://github.com/occasiolabs/occasio.git"
59
+ "url": "git+https://github.com/occasiolabs/occasio.git"
60
60
  },
61
61
  "homepage": "https://github.com/occasiolabs/occasio#readme",
62
62
  "bugs": {
@@ -0,0 +1,115 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * check-summary.js — pure function that turns an attestation predicate into
5
+ * the GitHub Check Run body (title + Markdown summary) used by the reference
6
+ * Action. Lives here in `src/` so both the Action (integrations/) and the
7
+ * `occasio demo attest` CLI can import it without crossing the src/ ←
8
+ * integrations/ boundary.
9
+ *
10
+ * No I/O, no env access, no side effects. Safe to call from any context.
11
+ *
12
+ * Why it's pulled out of integrations/attest-action/scripts/post-check.js:
13
+ * integrations/ is deliberately excluded from the npm tarball — it ships
14
+ * as a GitHub Action artifact, not as part of the CLI package. Requiring
15
+ * ../../integrations/... from src/demo/ worked in the source tree but
16
+ * broke immediately on `npm install` because the path doesn't exist in
17
+ * the published tarball. Pure helpers belong in src/ where they ship
18
+ * with the package; the Action imports them from there.
19
+ */
20
+
21
+ // ── Markdown escaping ───────────────────────────────────────────────────────
22
+ // Values from the attestation come from the agent's tool calls — a malicious
23
+ // or compromised tool name can contain Markdown control characters that
24
+ // break out of the table cell, inject links, or smuggle raw HTML through
25
+ // GitHub's renderer. Escape before interpolating into the Check Run summary.
26
+ //
27
+ // `mdCode` is for values rendered inside `` `...` `` cells: only the
28
+ // backtick itself needs handling (we strip rather than escape, because
29
+ // inline-code in GitHub-Flavored Markdown has no escape mechanism).
30
+ function mdCode(s) {
31
+ if (s === null || s === undefined) return '';
32
+ return String(s)
33
+ .replace(/[\r\n]+/g, ' ') // collapse newlines so cells stay one-line
34
+ .replace(/`/g, 'ˋ') // backtick → ˋ (visually similar, breaks out impossible)
35
+ .replace(/\|/g, '\\|') // GFM table cell separator
36
+ .slice(0, 256); // truncate runaway values
37
+ }
38
+
39
+ // `mdText` is for values rendered as raw Markdown text (no code fence
40
+ // around them). Escape the full GFM punctuation set.
41
+ function mdText(s) {
42
+ if (s === null || s === undefined) return '';
43
+ return String(s)
44
+ .replace(/[\r\n]+/g, ' ')
45
+ .replace(/([\\`*_{}\[\]()#+\-.!|<>])/g, '\\$1')
46
+ .slice(0, 256);
47
+ }
48
+
49
+ function intOr0(n) { return Number.isFinite(+n) ? Math.trunc(+n) : 0; }
50
+
51
+ // Only render Rekor as a link if it is the public Sigstore search domain
52
+ // over https. Anything else → display as escaped text, no clickable link.
53
+ function safeRekorUrl(raw) {
54
+ if (typeof raw !== 'string') return null;
55
+ try {
56
+ const u = new URL(raw);
57
+ if (u.protocol !== 'https:') return null;
58
+ if (u.host !== 'search.sigstore.dev') return null;
59
+ return u.toString();
60
+ } catch { return null; }
61
+ }
62
+
63
+ // ── Summary builder ─────────────────────────────────────────────────────────
64
+ // Pure function — no env access, no I/O — so it can be unit-tested directly.
65
+ // Returns { title, summary, signed } ready for the Checks API body.
66
+ function buildSummary(att, rekorRaw) {
67
+ const sum = att.execution_summary || {};
68
+ const signed = !!(att.signature && att.signature.type);
69
+
70
+ const title = String(
71
+ `Occasio Attested · ${intOr0(sum.tool_calls)} calls · ${intOr0(sum.blocked)} blocked`
72
+ ).slice(0, 255);
73
+
74
+ const lines = [];
75
+ lines.push(`**Predicate:** \`${mdCode(att.predicate_type)}\``);
76
+ lines.push('');
77
+ lines.push(`| Field | Value |`);
78
+ lines.push(`|---|---|`);
79
+ lines.push(`| Agent | \`${mdCode(att.agent?.platform || 'unknown')}\` ${att.agent?.model ? '· `' + mdCode(att.agent.model) + '`' : ''} |`);
80
+ lines.push(`| run_id | \`${mdCode(att.subject?.run_id || 'unknown')}\` |`);
81
+ if (att.subject?.git_commit) {
82
+ lines.push(`| commit | \`${mdCode(String(att.subject.git_commit).slice(0,12))}\` |`);
83
+ }
84
+ lines.push(`| Policy hash | \`${mdCode((att.policy?.file_hash || '').slice(0,16))}…\` (${mdText(att.policy?.source || 'unknown')}) |`);
85
+ lines.push(`| Tool calls | **${intOr0(sum.tool_calls)}** (LOCAL ${intOr0(sum.local)} · PASS ${intOr0(sum.passed)} · BLOCK ${intOr0(sum.blocked)} · TRANSFORM ${intOr0(sum.transformed)}) |`);
86
+ lines.push(`| Secrets redacted | ${intOr0(sum.secrets_redacted)} |`);
87
+ lines.push(`| Chain | \`${mdCode((att.audit_chain?.first_hash || '').slice(0,12) || '∅')}…${mdCode((att.audit_chain?.last_hash || '').slice(0,12) || '∅')}\` · ${intOr0(att.audit_chain?.event_count)} events |`);
88
+ lines.push(`| Signature | ${signed ? `✓ \`${mdCode(att.signature.type)}\`` : '— unsigned (informational only)'} |`);
89
+ const rekorSafe = safeRekorUrl(rekorRaw);
90
+ if (rekorSafe) lines.push(`| Rekor | [search.sigstore.dev](${rekorSafe}) |`);
91
+ else if (rekorRaw) lines.push(`| Rekor | \`${mdCode(rekorRaw)}\` (non-Rekor URL — not linked) |`);
92
+ lines.push('');
93
+
94
+ if (Array.isArray(sum.blocked_events) && sum.blocked_events.length) {
95
+ lines.push('### Blocked attempts');
96
+ lines.push('');
97
+ lines.push('| Tool | Target | Rule | When |');
98
+ lines.push('|---|---|---|---|');
99
+ for (const b of sum.blocked_events.slice(0, 25)) {
100
+ lines.push(`| \`${mdCode(b.tool)}\` | \`${mdCode((b.target || '').slice(0, 80))}\` | \`${mdCode(b.rule)}\` | T+${intOr0(b.at_offset_s)}s |`);
101
+ }
102
+ if (sum.blocked_events.length > 25) {
103
+ lines.push(`| … | (${intOr0(sum.blocked_events.length - 25)} more in artifact) | | |`);
104
+ }
105
+ lines.push('');
106
+ }
107
+
108
+ lines.push(`<sub>Spec: <a href="https://github.com/occasiolabs/occasio/blob/main/spec/agent-attestation/v1/README.md">agent-attestation/v1</a> · Independent verifier: <code>occasio attest verify</code> · Artifacts attached to this workflow run.</sub>`);
109
+
110
+ return { title, summary: lines.join('\n'), signed };
111
+ }
112
+
113
+ module.exports = {
114
+ mdCode, mdText, intOr0, safeRekorUrl, buildSummary,
115
+ };
@@ -39,7 +39,7 @@ const crypto = require('crypto');
39
39
  const { buildAttestation } = require('../attest');
40
40
  const { verifyFile } = require('../audit/verifier');
41
41
  const { canonicalize } = require('../attest/canonicalize');
42
- const { buildSummary } = require('../../integrations/attest-action/scripts/post-check');
42
+ const { buildSummary } = require('../attest/check-summary');
43
43
 
44
44
  const C = {
45
45
  r: s => `\x1b[31m${s}\x1b[0m`,
package/src/index.js CHANGED
@@ -48,7 +48,13 @@ const { runInspectCli } = require('./inspect');
48
48
  const { runAuditCli } = require('./audit/verifier');
49
49
  const { budgetStatus, fmtBudget, BUDGET_EXCEEDED_EVENT } = require('./budget');
50
50
 
51
- const VERSION = '0.8.0';
51
+ // Source of truth: package.json. Read at startup so `occasio --version`
52
+ // can't drift from what npm reports — the previous hardcoded constant
53
+ // caused 0.8.1's CLI to mis-report itself as 0.8.0.
54
+ const VERSION = (() => {
55
+ try { return require('../package.json').version; }
56
+ catch { return '0.0.0-unknown'; }
57
+ })();
52
58
  const LOG_SCHEMA_VERSION = 2;
53
59
  // Port override via env var (used by `occasio harness` and redteam to
54
60
  // run isolated proxies against scratch audit chains on free ports). Default