stacktrace-cli 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl
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.
- stacktrace_cli/__init__.py +2 -2
- stacktrace_cli/monitor/render.py +6 -2
- stacktrace_cli/monitor/site/app.js +48 -46
- stacktrace_cli/monitor/site/index.html +0 -2
- stacktrace_cli-0.2.2.dist-info/METADATA +154 -0
- {stacktrace_cli-0.2.0.dist-info → stacktrace_cli-0.2.2.dist-info}/RECORD +8 -8
- stacktrace_cli-0.2.0.dist-info/METADATA +0 -228
- {stacktrace_cli-0.2.0.dist-info → stacktrace_cli-0.2.2.dist-info}/WHEEL +0 -0
- {stacktrace_cli-0.2.0.dist-info → stacktrace_cli-0.2.2.dist-info}/entry_points.txt +0 -0
stacktrace_cli/__init__.py
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""The `stacktrace` command-line interface — detection and response for AI agents."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.2.
|
|
3
|
+
__version__ = "0.2.2"
|
stacktrace_cli/monitor/render.py
CHANGED
|
@@ -176,6 +176,12 @@ def _banners(analysis: Analysis) -> list[str]:
|
|
|
176
176
|
A blank column and a clean run render identically, which is the one
|
|
177
177
|
confusion this page refuses: a reader who cannot tell "nothing happened"
|
|
178
178
|
from "nothing could be read" has been told nothing.
|
|
179
|
+
|
|
180
|
+
Both banners are about a *reader* that came back with nothing. A kind with
|
|
181
|
+
no composition is not one of those and no longer appears: the calls it
|
|
182
|
+
accounts for are read, and the page shows what it can place them to.
|
|
183
|
+
`view.without_composition` is still reported by `stacktrace correlate`,
|
|
184
|
+
where the record rather than the console is the subject.
|
|
179
185
|
"""
|
|
180
186
|
banners: list[str] = []
|
|
181
187
|
for failure in analysis.run.collection_failures:
|
|
@@ -183,8 +189,6 @@ def _banners(analysis: Analysis) -> list[str]:
|
|
|
183
189
|
anonymous = len(analysis.view.kind_anonymous)
|
|
184
190
|
if anonymous:
|
|
185
191
|
banners.append(f"{anonymous} session(s) skipped: no agent kind to attribute them to")
|
|
186
|
-
for kind in analysis.view.without_composition:
|
|
187
|
-
banners.append(f"{kind}: no composition, so every call reads as unresolved")
|
|
188
192
|
return banners
|
|
189
193
|
|
|
190
194
|
|
|
@@ -145,16 +145,20 @@ function renderBanners(banners) {
|
|
|
145
145
|
|
|
146
146
|
function renderCounts(state) {
|
|
147
147
|
const summary = state.summary || {};
|
|
148
|
-
/*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
|
|
148
|
+
/* Sessions and actions, and no coverage fraction. `6,867/7,055 placed` is
|
|
149
|
+
* the count of what this page leaves off, stated: the gap between the two
|
|
150
|
+
* numbers is exactly the unplaceable rows, so the header reinstated by
|
|
151
|
+
* arithmetic what the rows below had stopped saying. The snapshot still
|
|
152
|
+
* carries it — `stacktrace correlate` is where coverage is the subject.
|
|
153
|
+
*
|
|
154
|
+
* Actions are counted off the rows this page keeps rather than read from
|
|
155
|
+
* `summary.actions`, which is the server's total over every call in the
|
|
156
|
+
* window. Read straight through, the header claimed more actions than the
|
|
157
|
+
* feed below it could account for. */
|
|
158
|
+
const actions = eventsFrom(state.sessions || []).length;
|
|
154
159
|
text(el("live-counts"),
|
|
155
160
|
" — " + plural(summary.sessions || 0, "session") +
|
|
156
|
-
" · " + plural(
|
|
157
|
-
" · " + (coverage ? coverage.resolved + "/" + coverage.total + " placed" : "placing…"));
|
|
161
|
+
" · " + plural(actions, "action"));
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
/* --- the demo's vocabulary ----------------------------------------------- */
|
|
@@ -209,7 +213,6 @@ const RULE_PLAIN = {
|
|
|
209
213
|
"stacktrace-credential-egress": "A password or token was sent to an outside service.",
|
|
210
214
|
"stacktrace-capability-crossing": "Private data was read, then sent outside.",
|
|
211
215
|
"stacktrace-intent-drift": "It acted on something nobody asked about.",
|
|
212
|
-
"stacktrace-unsanctioned-mcp-tool-use": "A tool ran that isn't in your installed set.",
|
|
213
216
|
"stacktrace-guardrail-modification": "A session changed the agent's own rules or settings.",
|
|
214
217
|
"stacktrace-injection-marker": "Injected-instruction markers were found in what the agent read.",
|
|
215
218
|
"stacktrace-destructive-action": "A destructive action was taken (deleting or overwriting).",
|
|
@@ -227,20 +230,32 @@ const KIND_TAG = {
|
|
|
227
230
|
subagent: ["sub-agent", "kt-sub"],
|
|
228
231
|
mcp: ["mcp", "kt-conn"],
|
|
229
232
|
command: ["command", "kt-cmd"],
|
|
230
|
-
unresolved: ["⚠ unresolved", "kt-warn"],
|
|
231
233
|
};
|
|
232
234
|
|
|
233
235
|
/* Plural, because every one of these labels a count. */
|
|
234
236
|
const KIND_LABEL = {
|
|
235
237
|
mcp: "MCP servers", skill: "Skills", command: "Commands",
|
|
236
|
-
subagent: "Sub-agents", tool: "Built-in tools",
|
|
238
|
+
subagent: "Sub-agents", tool: "Built-in tools",
|
|
237
239
|
};
|
|
238
240
|
|
|
239
241
|
/* Most consequential first, rather than by count: sorted by count the
|
|
240
242
|
* built-ins win every window and push the MCP servers — the only category
|
|
241
243
|
* describing a call that left the machine — under a number three orders of
|
|
242
244
|
* magnitude larger. */
|
|
243
|
-
const KIND_ORDER = ["mcp", "skill", "command", "subagent", "tool"
|
|
245
|
+
const KIND_ORDER = ["mcp", "skill", "command", "subagent", "tool"];
|
|
246
|
+
|
|
247
|
+
/* The category the correlator gives a call it could not place. Every surface
|
|
248
|
+
* on this page drops these rows: an unplaceable call names no component, so
|
|
249
|
+
* the row could only say that something happened and nothing here claims it,
|
|
250
|
+
* which is not a thing a reader can act on. The count is still computed
|
|
251
|
+
* server-side and still reaches the detector — this is a decision about the
|
|
252
|
+
* page, not about the record. */
|
|
253
|
+
const UNPLACEABLE = "unresolved";
|
|
254
|
+
|
|
255
|
+
/* Whether the snapshot row is one of those. */
|
|
256
|
+
function unplaceable(row) {
|
|
257
|
+
return row.category === UNPLACEABLE;
|
|
258
|
+
}
|
|
244
259
|
const TOP_PER_KIND = 3;
|
|
245
260
|
|
|
246
261
|
const SEVERITY_WORD = { critical: "Serious", high: "High", medium: "Medium", low: "Low" };
|
|
@@ -294,10 +309,13 @@ function evParts(e) {
|
|
|
294
309
|
provenance: "a link to an outside service",
|
|
295
310
|
};
|
|
296
311
|
}
|
|
312
|
+
/* A category this build has never heard of. Named as itself rather than
|
|
313
|
+
* described: anything else this function said about it would be invented,
|
|
314
|
+
* and the one category it used to describe no longer reaches the page. */
|
|
297
315
|
return {
|
|
298
316
|
name: e.name,
|
|
299
|
-
action: "used
|
|
300
|
-
provenance: "
|
|
317
|
+
action: "used " + (e.tool || "a tool"),
|
|
318
|
+
provenance: e.kind ? "category " + e.kind : "",
|
|
301
319
|
};
|
|
302
320
|
}
|
|
303
321
|
|
|
@@ -321,6 +339,7 @@ function eventsFrom(sessions) {
|
|
|
321
339
|
const activity = session.activity || [];
|
|
322
340
|
for (let index = 0; index < activity.length; index += 1) {
|
|
323
341
|
const row = activity[index];
|
|
342
|
+
if (unplaceable(row)) continue;
|
|
324
343
|
events.push({
|
|
325
344
|
id: row.span,
|
|
326
345
|
kind: row.category,
|
|
@@ -336,11 +355,6 @@ function eventsFrom(sessions) {
|
|
|
336
355
|
* the skills a reader had just run sat under MCP calls from half a day
|
|
337
356
|
* earlier. Named for what it is, because it is not the start. */
|
|
338
357
|
at: session.last_active || session.started_at,
|
|
339
|
-
/* Both worth a reader's attention; only the first is a claim about
|
|
340
|
-
* attribution. Correlation keeps a refused call's resolution, so the
|
|
341
|
-
* row beside the label names the component it would have reached. */
|
|
342
|
-
unplaced: row.category === "unresolved",
|
|
343
|
-
refused: row.status === "denied",
|
|
344
358
|
order: index,
|
|
345
359
|
});
|
|
346
360
|
}
|
|
@@ -356,7 +370,8 @@ function compositionOf(sessions) {
|
|
|
356
370
|
const seen = new Set();
|
|
357
371
|
for (const session of sessions) {
|
|
358
372
|
for (const row of session.activity || []) {
|
|
359
|
-
|
|
373
|
+
if (unplaceable(row)) continue;
|
|
374
|
+
const type = row.component_type || row.category;
|
|
360
375
|
const key = type + "\n" + (row.component_identity || row.component_name || row.tool_name);
|
|
361
376
|
if (!type || seen.has(key)) continue;
|
|
362
377
|
seen.add(key);
|
|
@@ -372,6 +387,7 @@ function activitySummaryOf(sessions) {
|
|
|
372
387
|
const named = {};
|
|
373
388
|
for (const session of sessions) {
|
|
374
389
|
for (const row of session.activity || []) {
|
|
390
|
+
if (unplaceable(row)) continue;
|
|
375
391
|
byKind[row.category] = (byKind[row.category] || 0) + 1;
|
|
376
392
|
if (!named[row.category]) named[row.category] = new Map();
|
|
377
393
|
const label = entryLabel(row);
|
|
@@ -531,9 +547,9 @@ let revealTimer = null;
|
|
|
531
547
|
* Routine built-ins (Bash, Read, Edit) are the constant background of every
|
|
532
548
|
* session: 5,795 of one window's 7,065 calls. They collapse **per project**
|
|
533
549
|
* into one counting row for the agent. Notable events — skills, MCP servers,
|
|
534
|
-
* sub-agents
|
|
535
|
-
*
|
|
536
|
-
*
|
|
550
|
+
* sub-agents and commands — are rare and important, so each gets its own row,
|
|
551
|
+
* keyed by span and never coalesced: two runs of `superpowers:brainstorming`
|
|
552
|
+
* are two things that happened.
|
|
537
553
|
*
|
|
538
554
|
* Grouping by tool name instead gave `Bash ×38 / Read ×16 / Bash ×3 / Edit
|
|
539
555
|
* ×20` per session, with every skill and connector buried under it. */
|
|
@@ -609,14 +625,6 @@ function countAgain(entry, e) {
|
|
|
609
625
|
entry.timeEl.textContent = fmtRange(entry.first, entry.last);
|
|
610
626
|
}
|
|
611
627
|
}
|
|
612
|
-
/* A refusal is worth seeing and was not worth a row of its own: `Bash ×3
|
|
613
|
-
* denied` beside `Bash ×38` was half the noise. It rides the row instead. */
|
|
614
|
-
if (e.refused) {
|
|
615
|
-
entry.refused += 1;
|
|
616
|
-
entry.node.classList.add("warn");
|
|
617
|
-
entry.warnEl.hidden = false;
|
|
618
|
-
entry.warnEl.textContent = "⚠ " + plural(entry.refused, "refusal");
|
|
619
|
-
}
|
|
620
628
|
/* Animated where it sits, never hoisted. Hoisting the most recently counted
|
|
621
629
|
* row would rank a busy week-old session above a quiet one from this
|
|
622
630
|
* morning: during a backfill, "counted just now" is a fact about the drain
|
|
@@ -656,14 +664,19 @@ function place(entry) {
|
|
|
656
664
|
|
|
657
665
|
function newRow(e, signature) {
|
|
658
666
|
const parts = evParts(e);
|
|
659
|
-
|
|
667
|
+
/* A category this build has never heard of takes its own name and the
|
|
668
|
+
* neutral tag: nothing on this page knows enough about it to warn. */
|
|
669
|
+
const kind = KIND_TAG[e.kind] || [e.kind || "", "kt-tool"];
|
|
660
670
|
/* The placeholder goes as soon as there is a real row — otherwise it rides
|
|
661
671
|
* the list and counts against the trim. */
|
|
662
672
|
if (!feedOrder.length) activityFeed.innerHTML = "";
|
|
663
673
|
|
|
674
|
+
/* No row this feed builds is reddened. The two sources of it were a call
|
|
675
|
+
* the correlator could not place — which no longer reaches the feed — and a
|
|
676
|
+
* refusal, which `outcome.py` is explicit is a guardrail working as
|
|
677
|
+
* intended: painting it red reports the permission prompt as the incident. */
|
|
664
678
|
const node = document.createElement("div");
|
|
665
679
|
node.className = "ev fresh";
|
|
666
|
-
if (e.unplaced || e.refused) node.classList.add("warn");
|
|
667
680
|
|
|
668
681
|
const countEl = element("span", "evcount", "");
|
|
669
682
|
countEl.hidden = true;
|
|
@@ -674,16 +687,6 @@ function newRow(e, signature) {
|
|
|
674
687
|
first.appendChild(element("span", "evact", parts.action));
|
|
675
688
|
first.appendChild(countEl);
|
|
676
689
|
|
|
677
|
-
const warnEl = element("span", "ev-warn", "");
|
|
678
|
-
warnEl.hidden = true;
|
|
679
|
-
if (e.unplaced) {
|
|
680
|
-
warnEl.hidden = false;
|
|
681
|
-
warnEl.textContent = "unplaced";
|
|
682
|
-
} else if (e.refused) {
|
|
683
|
-
warnEl.hidden = false;
|
|
684
|
-
warnEl.textContent = e.kind === "tool" ? "⚠ 1 refusal" : "refused";
|
|
685
|
-
}
|
|
686
|
-
|
|
687
690
|
const lastEl = element("span", "evlast", "");
|
|
688
691
|
const timeEl = element("span", "ev-time", fmtAgo(e.at));
|
|
689
692
|
|
|
@@ -692,15 +695,14 @@ function newRow(e, signature) {
|
|
|
692
695
|
second.appendChild(element("span", "ev-prov", parts.provenance));
|
|
693
696
|
second.appendChild(lastEl);
|
|
694
697
|
second.appendChild(timeEl);
|
|
695
|
-
second.appendChild(warnEl);
|
|
696
698
|
|
|
697
699
|
node.appendChild(first);
|
|
698
700
|
node.appendChild(second);
|
|
699
701
|
|
|
700
702
|
const started = Date.parse(e.at);
|
|
701
703
|
const entry = {
|
|
702
|
-
node: node, countEl: countEl,
|
|
703
|
-
count: 1,
|
|
704
|
+
node: node, countEl: countEl, lastEl: lastEl, timeEl: timeEl,
|
|
705
|
+
count: 1, key: keyOf(e), signature: signature,
|
|
704
706
|
first: Number.isNaN(started) ? Infinity : started,
|
|
705
707
|
last: Number.isNaN(started) ? -Infinity : started,
|
|
706
708
|
};
|
|
@@ -74,8 +74,6 @@
|
|
|
74
74
|
|
|
75
75
|
<div class="legend" id="live-legend">
|
|
76
76
|
<span><b class="lg-conn">MCP</b> = a link to an outside service</span>
|
|
77
|
-
<span><b class="lg-ok">✓</b> resolved to something you installed</span>
|
|
78
|
-
<span><b class="lg-warn">⚠</b> unresolved — nothing on this machine claims it</span>
|
|
79
77
|
</div>
|
|
80
78
|
|
|
81
79
|
<div class="logs">
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: stacktrace-cli
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: CLI for Stacktrace — Detection and Response platform for AI Agents.
|
|
5
|
+
Project-URL: Homepage, https://stacktrace.ai
|
|
6
|
+
Author-email: "Stacktrace AI, Inc" <founders@stacktrace.ai>
|
|
7
|
+
License-Expression: LicenseRef-Proprietary
|
|
8
|
+
Keywords: agent-security,ai-security,openaca,stacktrace
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: Other/Proprietary License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Security
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: click>=8.1
|
|
21
|
+
Requires-Dist: httpx<1.0.dev0,>=0.28.1
|
|
22
|
+
Requires-Dist: openaca==0.6.0
|
|
23
|
+
Requires-Dist: openaidr==0.1.0
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# stacktrace-cli
|
|
27
|
+
|
|
28
|
+
Detection and response for AI coding agents, from the command line.
|
|
29
|
+
|
|
30
|
+
Coding agents read files, run shell commands and call MCP servers on their own
|
|
31
|
+
initiative, and they write a transcript of every bit of it to disk.
|
|
32
|
+
`stacktrace` reads those transcripts, correlates what ran against the
|
|
33
|
+
components the agent is built from, and reports the security and reliability
|
|
34
|
+
findings in it — locally, on the machine the agent worked on.
|
|
35
|
+
|
|
36
|
+
The PyPI distribution is `stacktrace-cli`; the command it installs is
|
|
37
|
+
`stacktrace`. The two names differ because the bare `stacktrace` name on PyPI
|
|
38
|
+
belongs to an unrelated project.
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
uv tool install stacktrace-cli # isolated; recommended
|
|
44
|
+
# or
|
|
45
|
+
pip install stacktrace-cli
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Requires Python 3.11 or newer.
|
|
49
|
+
|
|
50
|
+
```console
|
|
51
|
+
$ stacktrace --version
|
|
52
|
+
stacktrace 0.2.1 (openaca 0.6.0)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Quick start
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
stacktrace sessions # what the agents on this machine did
|
|
59
|
+
stacktrace detect # what is wrong with it
|
|
60
|
+
stacktrace monitor # the same, live in a browser
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Commands
|
|
64
|
+
|
|
65
|
+
| Command | |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `sessions` | Print what the agents on this machine actually did. |
|
|
68
|
+
| `detect` | Find security and reliability findings in what agents did. |
|
|
69
|
+
| `monitor` | Watch this machine's agents in a browser, live. |
|
|
70
|
+
| `remote` | Configure remote endpoint services and upload to Stacktrace Cloud. |
|
|
71
|
+
| `scan` | Scan a repository or endpoint for agent-composition findings. |
|
|
72
|
+
| `bom` | Generate an Agent BOM for a repository or endpoint. |
|
|
73
|
+
| `policy` | Validate and compile restrictive endpoint policies. |
|
|
74
|
+
|
|
75
|
+
The last three are composition analysis, supplied by
|
|
76
|
+
[`openaca`](https://pypi.org/project/openaca/) and available under either
|
|
77
|
+
name.
|
|
78
|
+
|
|
79
|
+
## What it looks like
|
|
80
|
+
|
|
81
|
+
```console
|
|
82
|
+
$ stacktrace sessions --since 2d --include-content
|
|
83
|
+
claude-code:s1 [claude-code] 2026-08-27T09:00:00+00:00 2 turns 2 calls
|
|
84
|
+
assistant: Reading the changelog before drafting the release notes.
|
|
85
|
+
ok 28c Read
|
|
86
|
+
result: ## 0.4.0 - correlate, detect
|
|
87
|
+
assistant: Filing the release-notes follow-up.
|
|
88
|
+
- github/create_issue
|
|
89
|
+
|
|
90
|
+
Summary — 1 sessions, 2 turns, 2 tool calls
|
|
91
|
+
|
|
92
|
+
agent kinds
|
|
93
|
+
1 claude-code
|
|
94
|
+
|
|
95
|
+
tools called (2 distinct)
|
|
96
|
+
1 Read
|
|
97
|
+
1 github/create_issue
|
|
98
|
+
|
|
99
|
+
MCP servers reached (1 distinct)
|
|
100
|
+
1 github
|
|
101
|
+
|
|
102
|
+
0 subagent turns · 0 results abridged upstream · 1 ok
|
|
103
|
+
|
|
104
|
+
1 of 2 calls returned with no outcome the collector could establish; the agent's parser supplies no success signal.
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
A blank status column is the collector's `unknown`, not a pending call: the
|
|
108
|
+
client recorded no outcome that could be established, and the closing line
|
|
109
|
+
counts those rather than filling one in.
|
|
110
|
+
|
|
111
|
+
## What `detect` finds
|
|
112
|
+
|
|
113
|
+
Four kinds of finding, under two families that carry separate severity
|
|
114
|
+
ladders — a stalled loop and a leaked credential do not belong on one scale.
|
|
115
|
+
|
|
116
|
+
**Security** — a credential reaching an outbound call; an injected instruction
|
|
117
|
+
the agent then followed; a vulnerable component actually reached, with the
|
|
118
|
+
vulnerability behind it.
|
|
119
|
+
|
|
120
|
+
**Reliability** — a loop that stalled; a call that hung.
|
|
121
|
+
|
|
122
|
+
Findings are correlated against an Agent BOM before they are judged, so a
|
|
123
|
+
vulnerable component is reported when something actually used it rather than
|
|
124
|
+
because it is installed.
|
|
125
|
+
|
|
126
|
+
## What leaves your machine
|
|
127
|
+
|
|
128
|
+
Two of `detect`'s three stages run entirely locally and need no model or
|
|
129
|
+
credential. The third sends flagged sessions to the agent's *own* CLI — the
|
|
130
|
+
provider that produced the transcript, never a different one — capped by
|
|
131
|
+
`--budget`; `--no-escalate` turns it off and leaves the two local stages.
|
|
132
|
+
|
|
133
|
+
`sessions` omits prompts, tool arguments and results unless you pass
|
|
134
|
+
`--include-content`. `monitor` binds to loopback only, refuses a non-loopback
|
|
135
|
+
address rather than warning about it, and escalates nothing unless `--escalate`
|
|
136
|
+
is given.
|
|
137
|
+
|
|
138
|
+
## Status
|
|
139
|
+
|
|
140
|
+
Beta, and under active development.
|
|
141
|
+
|
|
142
|
+
`sessions`, `detect` and `monitor` work end to end today. Session collection
|
|
143
|
+
currently reads Claude Code transcripts; further agent kinds are in progress
|
|
144
|
+
upstream in [OpenAIDR](https://github.com/open-agent-security/openaidr).
|
|
145
|
+
|
|
146
|
+
## Built on
|
|
147
|
+
|
|
148
|
+
Two Apache-2.0 packages, neither of which depends on this one:
|
|
149
|
+
[`openaca`](https://pypi.org/project/openaca/) for agent composition analysis,
|
|
150
|
+
and [`openaidr`](https://pypi.org/project/openaidr/) for session collection.
|
|
151
|
+
|
|
152
|
+
## Licence
|
|
153
|
+
|
|
154
|
+
Proprietary. © Stacktrace AI, Inc. — [stacktrace.ai](https://stacktrace.ai)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
stacktrace_cli/__init__.py,sha256=
|
|
1
|
+
stacktrace_cli/__init__.py,sha256=7-HhS3y6stqt0b0tldy0IhMr_bOjUk_HMTHwNj4LgyI,111
|
|
2
2
|
stacktrace_cli/__main__.py,sha256=F_tqj3PRzeBtY-uvBs923H8oc-s9bebhIh6UnAUwNYc,448
|
|
3
3
|
stacktrace_cli/analysis.py,sha256=PECyhd_398fDVjg3K78HVzB4xHYS7sfLNXpASRXNRsY,15303
|
|
4
4
|
stacktrace_cli/cli.py,sha256=tJjGtGlaI6OB4bdlgX3q63VffZxRIjwiCwH54kzxRAo,17032
|
|
@@ -32,13 +32,13 @@ stacktrace_cli/detector/prompts/v1/stacktrace-injected-instruction-followed.md,s
|
|
|
32
32
|
stacktrace_cli/detector/prompts/v1/stacktrace-intent-drift.md,sha256=1dI4bUIonwm6t2x32GnV-yGNfb1jGibeBo6Wli62WZI,455
|
|
33
33
|
stacktrace_cli/monitor/__init__.py,sha256=akhxzlSGyvbG-3WyC-5Ch5nuYP1E6xga8rzkJ6Tn-AU,469
|
|
34
34
|
stacktrace_cli/monitor/escalate.py,sha256=tpkp88xKyHX0BRVFow8b9_8YswduAAofidMiZBpRix4,7398
|
|
35
|
-
stacktrace_cli/monitor/render.py,sha256=
|
|
35
|
+
stacktrace_cli/monitor/render.py,sha256=oR3Sr2Tlk2_NtpVmJfLnU_IH-bkXIs7p4a27OZgn4mY,10064
|
|
36
36
|
stacktrace_cli/monitor/server.py,sha256=ZTKUy3Ct-7rfsErN3Cpr5PaaZ5SX_OMw-ExFe6FKL9w,19683
|
|
37
37
|
stacktrace_cli/monitor/state.py,sha256=UVTLpAecypsLTBs5IdWlY55dyJTf-2jI8lDAWoH9PP4,4157
|
|
38
38
|
stacktrace_cli/monitor/verdicts.py,sha256=V6IK2C9eVxZ2RXjWkfDdmfvN8f-Ol67U-N5c1kXx8rQ,2275
|
|
39
39
|
stacktrace_cli/monitor/watch.py,sha256=LrbS6RnZg322tbESQ4u2rTVxhkxsNI5UwdhpTEO3GgE,15546
|
|
40
|
-
stacktrace_cli/monitor/site/app.js,sha256=
|
|
41
|
-
stacktrace_cli/monitor/site/index.html,sha256=
|
|
40
|
+
stacktrace_cli/monitor/site/app.js,sha256=CM28lPaxuy-oourHb85jw35NDhqnFpYxc0bKgQsLrX0,43898
|
|
41
|
+
stacktrace_cli/monitor/site/index.html,sha256=OhbyY9Dnvec90IMc9a8iGBZdgfk38aNmbyhwhrrLXYM,3985
|
|
42
42
|
stacktrace_cli/monitor/site/styles.css,sha256=ol_sSrn2Jwk5xAtz9Avq2itUp9r1Z-nv6sf7KSwNSlU,35603
|
|
43
43
|
stacktrace_cli/monitor/site/fonts/OFL.txt,sha256=3eGN4fg4dKG6QBHOpLJobIeUAs3eQnmZBiykH5b-TGY,9975
|
|
44
44
|
stacktrace_cli/monitor/site/fonts/dm-mono-400-latin.woff2,sha256=_XUh81MaXM_GVbJcTyLphx3z7BQa15uyf94g0N80e20,8688
|
|
@@ -61,7 +61,7 @@ stacktrace_cli/sessions/access.py,sha256=mpKMdRufPGTZNJCVPa2MoKWdM8eOYzaYMDsGqob
|
|
|
61
61
|
stacktrace_cli/sessions/outcome.py,sha256=IQUrOoigPAFHnoA26LlOjSSB8hnSQUIJ8E5tQ0LdUJk,2632
|
|
62
62
|
stacktrace_cli/sessions/protocols.py,sha256=rvQsVd77AekNGhp73masMa61-DbG62QF8Y6hDSwQ9bU,6630
|
|
63
63
|
stacktrace_cli/sessions/render.py,sha256=SCu9Fx-7hWQWOsy3O7zbYzJqCNomaMp_19V7NQTkSm0,14001
|
|
64
|
-
stacktrace_cli-0.2.
|
|
65
|
-
stacktrace_cli-0.2.
|
|
66
|
-
stacktrace_cli-0.2.
|
|
67
|
-
stacktrace_cli-0.2.
|
|
64
|
+
stacktrace_cli-0.2.2.dist-info/METADATA,sha256=uAgSCLSXgp-IdFJ8L3jwRT9Z_GEjNQeFv7608lCBcQY,5459
|
|
65
|
+
stacktrace_cli-0.2.2.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
66
|
+
stacktrace_cli-0.2.2.dist-info/entry_points.txt,sha256=OYDmb2CtEjd8TV78zxiGB1XVrLrC6vvayAPXa79_tJ0,60
|
|
67
|
+
stacktrace_cli-0.2.2.dist-info/RECORD,,
|
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: stacktrace-cli
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: Placeholder CLI for Stacktrace.ai — installs the `stacktrace` command.
|
|
5
|
-
Project-URL: Homepage, https://stacktrace.ai
|
|
6
|
-
Author-email: Prasanth G <prasanth@openaca.dev>
|
|
7
|
-
License-Expression: LicenseRef-Proprietary
|
|
8
|
-
Keywords: agent-security,ai-security,openaca,stacktrace
|
|
9
|
-
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
-
Classifier: Environment :: Console
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: License :: Other/Proprietary License
|
|
13
|
-
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Topic :: Security
|
|
15
|
-
Requires-Python: >=3.11
|
|
16
|
-
Requires-Dist: click>=8.1
|
|
17
|
-
Requires-Dist: httpx<1.0.dev0,>=0.28.1
|
|
18
|
-
Requires-Dist: openaca==0.6.0
|
|
19
|
-
Requires-Dist: openaidr==0.1.0
|
|
20
|
-
Description-Content-Type: text/markdown
|
|
21
|
-
|
|
22
|
-
# stacktrace-cli
|
|
23
|
-
|
|
24
|
-
Command-line interface for [Stacktrace.ai](https://stacktrace.ai).
|
|
25
|
-
|
|
26
|
-
The PyPI distribution is named `stacktrace-cli` Installing this package provides
|
|
27
|
-
the `stacktrace` executable.
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
pip install stacktrace-cli
|
|
31
|
-
stacktrace --version
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
This is an early placeholder release. It depends on
|
|
35
|
-
[`openaca`](https://pypi.org/project/openaca/), the open-source Agent
|
|
36
|
-
Composition Analysis toolkit that Stacktrace.ai builds on.
|
|
37
|
-
|
|
38
|
-
## Design
|
|
39
|
-
|
|
40
|
-
This repository is the design home for Stacktrace Detect — the agentic AI
|
|
41
|
-
detection and response product this CLI grows into. OpenACA answers *what is
|
|
42
|
-
installed and what could it do*; Stacktrace Detect adds *what did it actually
|
|
43
|
-
do*, by joining agent session activity to the composition graph.
|
|
44
|
-
|
|
45
|
-
| Document | Covers |
|
|
46
|
-
|---|---|
|
|
47
|
-
| [docs/specs/aidr.md](docs/specs/aidr.md) | Umbrella — components, contracts, tenets, delivery |
|
|
48
|
-
| [docs/specs/session-input.md](docs/specs/session-input.md) | The seam over OpenAIDR, which collects sessions |
|
|
49
|
-
| [docs/specs/correlation.md](docs/specs/correlation.md) | The join: sessions against the composition graph |
|
|
50
|
-
| [docs/specs/detector.md](docs/specs/detector.md) | Three-stage detection and the finding family |
|
|
51
|
-
|
|
52
|
-
Decisions are in [docs/adrs/](docs/adrs/).
|
|
53
|
-
|
|
54
|
-
Built on two Apache-2.0 packages, neither of which depends on this one:
|
|
55
|
-
[`openaca`](https://pypi.org/project/openaca/) for composition analysis, and
|
|
56
|
-
[`openaidr`](https://github.com/open-agent-security/openaidr) for session
|
|
57
|
-
collection.
|
|
58
|
-
|
|
59
|
-
## Status
|
|
60
|
-
|
|
61
|
-
`stacktrace` is one front door over two kinds of command:
|
|
62
|
-
|
|
63
|
-
```
|
|
64
|
-
Analysis (OpenACA):
|
|
65
|
-
bom Generate an Agent BOM for a repository or endpoint.
|
|
66
|
-
policy Validate and compile restrictive endpoint policies.
|
|
67
|
-
scan Scan a repository or endpoint for agent-composition findings.
|
|
68
|
-
|
|
69
|
-
Stacktrace:
|
|
70
|
-
detect Find security and reliability findings in what agents did.
|
|
71
|
-
remote Configure remote endpoint services.
|
|
72
|
-
sessions Print what the agents on this machine actually did.
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
`stacktrace` is the entry point for hosted-product users. OpenACA remains the
|
|
76
|
-
tool itself and stays independently installable — with it installed, both
|
|
77
|
-
`openaca scan` and `stacktrace scan` work and do exactly the same thing,
|
|
78
|
-
because they are the same Click command object running in one process.
|
|
79
|
-
`stacktrace --version` reports both versions, which turns "which did you run?"
|
|
80
|
-
into one answered question.
|
|
81
|
-
|
|
82
|
-
**Pre-alpha, and not installable from PyPI yet.** `stacktrace sessions` works —
|
|
83
|
-
it reads what the agents on this machine did, through
|
|
84
|
-
[OpenAIDR](https://github.com/open-agent-security/openaidr):
|
|
85
|
-
|
|
86
|
-
```console
|
|
87
|
-
$ uv run stacktrace sessions --since 2d --include-content
|
|
88
|
-
claude-code:s1 [claude-code] 2026-08-27T09:00:00+00:00 2 turns 2 calls
|
|
89
|
-
assistant: Reading the changelog before drafting the release notes.
|
|
90
|
-
ok 28c Read
|
|
91
|
-
result: ## 0.4.0 - correlate, detect
|
|
92
|
-
assistant: Filing the release-notes follow-up.
|
|
93
|
-
- github/create_issue
|
|
94
|
-
|
|
95
|
-
Summary — 1 sessions, 2 turns, 2 tool calls
|
|
96
|
-
|
|
97
|
-
agent kinds
|
|
98
|
-
1 claude-code
|
|
99
|
-
|
|
100
|
-
tools called (2 distinct)
|
|
101
|
-
1 Read
|
|
102
|
-
1 github/create_issue
|
|
103
|
-
|
|
104
|
-
MCP servers reached (1 distinct)
|
|
105
|
-
1 github
|
|
106
|
-
|
|
107
|
-
0 subagent turns · 0 results abridged upstream · 1 ok
|
|
108
|
-
|
|
109
|
-
1 of 2 calls returned with no outcome the collector could establish; the agent's parser supplies no success signal.
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
The blank status on the second row is the collector's `unknown`, not a pending
|
|
113
|
-
call: the client recorded no outcome it could establish, and the closing line
|
|
114
|
-
says how many of those there were rather than filling one in.
|
|
115
|
-
|
|
116
|
-
The next step is the join: resolving what ran to the components the agent is
|
|
117
|
-
built from. `detect` performs it — it correlates the collected sessions against
|
|
118
|
-
an Agent BOM before it judges anything — but there is no longer a command that
|
|
119
|
-
prints the correlated view on its own. `stacktrace correlate` was withdrawn; the
|
|
120
|
-
correlation code stays, as the stage `detect` is built on.
|
|
121
|
-
|
|
122
|
-
Both `openaca` and `openaidr` resolve from PyPI now, like any other
|
|
123
|
-
dependency — no sibling checkout, no `[tool.uv.sources]` override. What still
|
|
124
|
-
gates a release is narrower: both float on `>=` during normal development but
|
|
125
|
-
must be pinned exactly (`==`) before a version is published, so a built wheel
|
|
126
|
-
names precisely what it was tested against.
|
|
127
|
-
`uv run pytest tests/test_release_readiness.py -m release_gate` checks that —
|
|
128
|
-
the `release-stacktrace` skill runs it as part of cutting a release.
|
|
129
|
-
|
|
130
|
-
## Adding a command
|
|
131
|
-
|
|
132
|
-
A command is a **pass-through** or it is **native**, never both — and a
|
|
133
|
-
pass-through never gains a flag of its own. See
|
|
134
|
-
[ADR-0021](docs/adrs/0021-two-command-kinds.md) for why.
|
|
135
|
-
|
|
136
|
-
- **Pass-through** — add one string to `PASSTHROUGH` in
|
|
137
|
-
`src/stacktrace_cli/cli.py`, having decided it belongs. The loop registers
|
|
138
|
-
OpenACA's own command object under that name; there is nothing else to
|
|
139
|
-
write and nothing to keep in step with OpenACA.
|
|
140
|
-
- **Native** — write a Click command or group and `add_command` it, the way
|
|
141
|
-
`remote` is. If it needs OpenACA it calls `openaca.core`
|
|
142
|
-
([ADR-0020](docs/adrs/0020-openaca-consumption-boundary.md)), and whatever
|
|
143
|
-
it names there is added to the contract test in `tests/remote/`, because it
|
|
144
|
-
now holds duplicated knowledge of an interface.
|
|
145
|
-
|
|
146
|
-
The section a command lands under in `--help` follows from which kind it is;
|
|
147
|
-
there is no second list to update.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
## Development
|
|
151
|
-
|
|
152
|
-
`uv sync` and the four gates — `ruff check`, `ruff format --check`, `pyright`,
|
|
153
|
-
`pytest` — need nothing beside this repository. `openaca` resolves from PyPI at
|
|
154
|
-
the floor `pyproject.toml` declares. A sibling `../openaca` checkout is no
|
|
155
|
-
longer read: the interim `[tool.uv.sources]` path source retired at the 0.6.0
|
|
156
|
-
cutover.
|
|
157
|
-
|
|
158
|
-
## Cutover from `openaca remote`
|
|
159
|
-
|
|
160
|
-
`stacktrace remote` is the hosted-service client OpenACA is removing. The
|
|
161
|
-
behaviour, the payload and the Cloud endpoints are unchanged; only the code
|
|
162
|
-
that produces them moved. Machines already running `openaca remote sync
|
|
163
|
-
endpoint` need four steps each.
|
|
164
|
-
|
|
165
|
-
1. **Have the collector token to hand, or mint a new one.** A collector token
|
|
166
|
-
cannot be recovered from the Cloud — it is returned once at issuance and
|
|
167
|
-
only a hash and its last four characters are kept — so it has to come from
|
|
168
|
-
wherever it was stored (an MDM secret store, a password manager). If it was
|
|
169
|
-
not stored, mint a new one and deploy that; nothing goes down while you do,
|
|
170
|
-
because the existing token keeps working until it is revoked.
|
|
171
|
-
2. **Configure this machine.** `stacktrace remote configure --token …`, or set
|
|
172
|
-
`STACKTRACE_REMOTE_TOKEN` and `STACKTRACE_REMOTE_API_URL` for a scripted
|
|
173
|
-
deployment.
|
|
174
|
-
3. **Repoint whatever schedules the sync** at `stacktrace remote sync
|
|
175
|
-
endpoint`.
|
|
176
|
-
4. **Delete `~/.config/openaca/remote.toml`.** Once OpenACA has no `remote`
|
|
177
|
-
command, that file is an unused plaintext credential sitting on disk.
|
|
178
|
-
Nothing breaks if it stays, which is exactly why it will be forgotten.
|
|
179
|
-
|
|
180
|
-
### What takes care of itself
|
|
181
|
-
|
|
182
|
-
- **The asset converges.** Registration is idempotent on `(org, asset_type,
|
|
183
|
-
external_id)` with the host name as `external_id`, so registering from here
|
|
184
|
-
resolves to the same asset OpenACA registered — one extra round trip on the
|
|
185
|
-
first run, no duplicate machine in the console.
|
|
186
|
-
- **Both commands may coexist during the overlap**, producing two BOM rows per
|
|
187
|
-
sync. That is noise rather than corruption.
|
|
188
|
-
|
|
189
|
-
> These two statements, and the unrecoverability of a collector token in step
|
|
190
|
-
> 1, describe what the deployed Cloud does. They have **not** been confirmed
|
|
191
|
-
> against the running service for this release — they are read from the
|
|
192
|
-
> hosted side's design, which records what some revision implements rather
|
|
193
|
-
> than what production does today. Confirm them before an operator acts on
|
|
194
|
-
> step 1 in particular: discarding the only copy of a token on the strength of
|
|
195
|
-
> an unverified sentence leaves no recovery.
|
|
196
|
-
|
|
197
|
-
### What does not
|
|
198
|
-
|
|
199
|
-
- **A pending OpenACA spool is orphaned.** Its files are in
|
|
200
|
-
`~/.local/state/openaca`, and this channel never reads them. Drain it by
|
|
201
|
-
running the old command once before cutting over, or accept losing what it
|
|
202
|
-
holds. Do not assume that is one sync's worth: the spool keeps one file per
|
|
203
|
-
failed agent and accumulates across offline runs, so count the files first.
|
|
204
|
-
- **The MDM deploy scripts and the scheduled agent** do not exist here yet.
|
|
205
|
-
|
|
206
|
-
## Release obligations
|
|
207
|
-
|
|
208
|
-
Finishing the remote-sync work was **not** the same as being releasable, and
|
|
209
|
-
the gap was structural rather than a matter of polish: while `pyproject.toml`
|
|
210
|
-
carried a path source to a sibling checkout, `pip install stacktrace-cli` from
|
|
211
|
-
PyPI would have resolved an `openaca` without the consumption facade, and every
|
|
212
|
-
`remote` command would have failed at import.
|
|
213
|
-
|
|
214
|
-
All three are discharged, at `openaca` 0.6.0 — the first release carrying the
|
|
215
|
-
facade:
|
|
216
|
-
|
|
217
|
-
1. `[tool.uv.sources]` is gone; `uv.lock` resolves `openaca` from PyPI.
|
|
218
|
-
2. `.github/workflows/ci.yml` checks out this repository alone. The sibling
|
|
219
|
-
checkout and its pinned-revision assertion went with the path source.
|
|
220
|
-
3. The floor is `openaca>=0.6.0`, which is where plan 005 Task 1's
|
|
221
|
-
required-import probe first reports no missing name. Re-run that probe
|
|
222
|
-
against any candidate release before moving the floor again; it passes only
|
|
223
|
-
when it reports nothing missing.
|
|
224
|
-
|
|
225
|
-
One proof retired with them. The payload-equivalence test imported openaca's
|
|
226
|
-
`tools.remote` — the module 0.6.0 removed — so it cannot run against any
|
|
227
|
-
release that carries the facade. What it established is a fact about the
|
|
228
|
-
migration, not a property that a later release could re-check.
|
|
File without changes
|
|
File without changes
|