@nurix/etna 0.19.40 → 0.19.42
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/data/harness-index.json +1 -1
- package/data/harnesses/code/harness.json +1 -1
- package/data/harnesses/delivery/harness.json +1 -1
- package/data/harnesses/discover/CLAUDE.md +50 -2
- package/data/harnesses/discover/agents/api.md +24 -0
- package/data/harnesses/discover/agents/codebase.md +24 -0
- package/data/harnesses/discover/agents/community.md +24 -0
- package/data/harnesses/discover/agents/deployed-client-bundle.md +24 -0
- package/data/harnesses/discover/agents/distribution-artifacts.md +24 -0
- package/data/harnesses/discover/agents/docs.md +27 -1
- package/data/harnesses/discover/agents/infra-backend-fingerprint.md +24 -0
- package/data/harnesses/discover/agents/packages.md +24 -0
- package/data/harnesses/discover/agents/session.md +40 -0
- package/data/harnesses/discover/agents/website.md +24 -0
- package/data/harnesses/discover/agents/wire-capture.md +40 -0
- package/data/harnesses/discover/codex/agents/api.toml +24 -0
- package/data/harnesses/discover/codex/agents/codebase.toml +24 -0
- package/data/harnesses/discover/codex/agents/community.toml +24 -0
- package/data/harnesses/discover/codex/agents/deployed-client-bundle.toml +24 -0
- package/data/harnesses/discover/codex/agents/distribution-artifacts.toml +24 -0
- package/data/harnesses/discover/codex/agents/docs.toml +27 -1
- package/data/harnesses/discover/codex/agents/infra-backend-fingerprint.toml +24 -0
- package/data/harnesses/discover/codex/agents/packages.toml +24 -0
- package/data/harnesses/discover/codex/agents/session.toml +40 -0
- package/data/harnesses/discover/codex/agents/website.toml +24 -0
- package/data/harnesses/discover/codex/agents/wire-capture.toml +40 -0
- package/data/harnesses/discover/harness.json +3 -4
- package/data/harnesses/discover/rules/calibration.md +219 -0
- package/data/harnesses/discover/rules/cartography-coverage.md +69 -1
- package/data/harnesses/discover/rules/discovery.md +59 -1
- package/data/harnesses/discover/rules/evaluation.md +13 -0
- package/data/harnesses/discover/rules/ingestion.md +248 -7
- package/data/harnesses/discover/rules/self-correction.md +6 -2
- package/data/harnesses/discover/rules/tooling.md +205 -0
- package/data/harnesses/discover/rules/tradecraft.md +116 -2
- package/data/harnesses/etna/harness.json +1 -1
- package/package.json +2 -2
- package/data/harnesses/discover/tooling-recommendations.md +0 -119
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Tooling Reference — Host Capability · Tiers · The Bright Line
|
|
2
|
+
|
|
3
|
+
The reference for **Step 0** (`discover` harness, run once per corpus folder before Mode 1). Capture depth
|
|
4
|
+
is bounded by host tooling most machines lack. This file carries the **tier lists** (what to recommend),
|
|
5
|
+
the **ethics classification** (clean · gated · avoid), the **per-dimension gap map** (what a bare host
|
|
6
|
+
cannot do), and the **install caveats** that have actually bitten.
|
|
7
|
+
|
|
8
|
+
**Nothing here is ever installed automatically.** The harness detects what is present, recommends only what
|
|
9
|
+
is missing, and records the result in `.claude/discover-tooling.json` so it never re-prompts in the same
|
|
10
|
+
folder. The harness recommends; the user decides. A user who declines proceeds to Mode 1 on whatever is
|
|
11
|
+
present — a missing capability is re-surfaced at the Mode 2 capability-gap checkpoint (`ingestion.md` §8),
|
|
12
|
+
never silently degraded.
|
|
13
|
+
|
|
14
|
+
A host almost always arrives strong on languages and package managers (node, pnpm, python, uv, cargo, gh,
|
|
15
|
+
jq, ripgrep) and **bare on capture/analysis tooling**. That gap is the difference between a shallow capture
|
|
16
|
+
and a deep one.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## The three classes (this is an ethics classification, not a convenience one)
|
|
21
|
+
|
|
22
|
+
| Class | Meaning | Rule |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| **clean** | passive / local-only; always in-bounds | recommend freely, offer the one-paste block |
|
|
25
|
+
| **gated** | in-bounds **only** first-party / user-owned / opt-in — the user installs the CA, routes *their own* client, owns the keys | surface the command, **never run it for the user** |
|
|
26
|
+
| **avoid** | crosses the harness bright line (active scanning, exploitation, third-party interception) | never recommend; listed so it is not added by reflex |
|
|
27
|
+
|
|
28
|
+
The harness is for **spec-writing / integration / competitive analysis** — passive or first-party only,
|
|
29
|
+
respecting TOS and robots (`ingestion.md` §7). No tool changes that; the gated ones simply need the user in
|
|
30
|
+
the loop, exactly as `session` Pass-2 and `wire-capture` rung 3 do.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Why this file matters more since the §5 rewrite
|
|
35
|
+
|
|
36
|
+
`ingestion.md` §5 now requires **verbatim dumping** — the artifact lands on disk whole, and the digest is
|
|
37
|
+
written *beside* it, never *instead* of it. The obvious objection is "a 6 MB spec is unusable." **These
|
|
38
|
+
tools are the answer to that objection.** They are what make a verbatim corpus queryable, which is what
|
|
39
|
+
makes dumping-not-digesting practical:
|
|
40
|
+
|
|
41
|
+
| Instead of digesting… | Dump verbatim, then |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| a 6 MB OpenAPI spec → a hand-typed endpoint table | `curl -o raw/spec.json` → **spectral** to catalog, **oasdiff** to diff, **gron** to grep |
|
|
44
|
+
| a 9 MB minified bundle → prose about the routes | `curl -o raw/bundle.js` → **webcrack** / **source-map-explorer** to read, **ast-grep** to query |
|
|
45
|
+
| a HAR → a narrative of the calls | keep `raw/flows.har` → **gron** to flatten and grep |
|
|
46
|
+
| a docs site → distilled sections only | keep the page sources → **trafilatura** / **pandoc** to render |
|
|
47
|
+
| a package's exports → string-mined prose | `npm view --json` → **typescript + attw + publint**, **griffe** for Python |
|
|
48
|
+
|
|
49
|
+
**A digest is a lossy compression you cannot undo; a verbatim artifact plus a query tool is lossless.**
|
|
50
|
+
Recommend Tier 1 on that basis, not as a nice-to-have.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Tier 1 — clean, install now (broad leverage)
|
|
55
|
+
|
|
56
|
+
Highest-consensus, always in-bounds. Offer as one paste:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Homebrew (CLI analysis + data-wrangling + web→text)
|
|
60
|
+
brew install gron pandoc ast-grep scc universal-ctags oasdiff fd yq dasel monolith tokei sevenzip
|
|
61
|
+
# Rust
|
|
62
|
+
cargo install htmlq
|
|
63
|
+
# Python (via uv — pipx is typically absent; uv tool install is the path)
|
|
64
|
+
uv tool install trafilatura && uv tool install griffe
|
|
65
|
+
# Node global (JS/TS surface + bundle de-obfuscation + spec lint)
|
|
66
|
+
npm i -g webcrack source-map-explorer @arethetypeswrong/cli publint @electron/asar typescript @stoplight/spectral-cli
|
|
67
|
+
# Headless browser for JS-rendered docs/marketing (clean — the user's own headless instance)
|
|
68
|
+
npm i -g playwright && npx playwright install chromium
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| Tool | Unlocks | Dimensions served |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| **gron** | flatten any JSON → greppable/diffable `path = value` (specs, captures, manifests, HAR, configs) | **7** (codebase, api, session, bundle, infra, wire, cross-cutting) |
|
|
74
|
+
| **trafilatura** | boilerplate-stripped main content + dates from HTML → clean markdown; feed/sitemap discovery | **4** (docs, website, community, infra) |
|
|
75
|
+
| **pandoc** | deterministic HTML→GFM (headings, tables, code fences) at scale | 3 (docs, website, community) |
|
|
76
|
+
| **ast-grep** | tree-sitter structural search/count across many files + langs (call sites, routes, exports) — catches what regex misses | 2 (codebase, bundle) |
|
|
77
|
+
| **oasdiff** | structured breaking-vs-non-breaking OpenAPI diff — makes the published-vs-app-own diff machine-checked | 2 (api, cross-cutting) |
|
|
78
|
+
| **webcrack** | un-minify / un-webpack / de-obfuscate SPA & Electron JS into readable modules | 2 (bundle, distribution) |
|
|
79
|
+
| **scc** | instant LOC-by-language + complexity census of a fresh clone (JSON) — the codebase opening move | 1 (codebase) |
|
|
80
|
+
| **universal-ctags** | real multi-lang JSON symbol index (the host's BSD ctags is broken for this) | 1 (codebase) |
|
|
81
|
+
| **htmlq** | surgical CSS-selector extraction (pricing cards, feature tables) instead of regex | 1 (website) |
|
|
82
|
+
| **source-map-explorer** | reassemble a shipped `.map` → original `src/` tree (the `source-map-reassembly` method) | 1 (bundle) |
|
|
83
|
+
| **spectral** | validate/resolve an OpenAPI/AsyncAPI spec → structured endpoint/security/`$ref` catalog | 1 (api) |
|
|
84
|
+
| **griffe** | extract a PyPI package's full public API (the `.d.ts` equivalent for Python) | 1 (packages) |
|
|
85
|
+
| **@arethetypeswrong/cli + typescript + publint** | parse the real `.d.ts`/exports type graph + grade the publish surface (verbatim, not string-mined) | 1 (packages) |
|
|
86
|
+
| **@electron/asar** | extract Electron `app.asar` → JS module tree (the distribution step the playbook names but cannot otherwise run) | 1 (distribution) |
|
|
87
|
+
| **fd · yq · dasel · monolith · tokei · sevenzip** | gitignore-aware file discovery · YAML/XML/TOML query · RSS/XML→JSON · single-file page archive · LOC cross-check · multi-format extract | several |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Tier 2 — gated, install when a run needs them (first-party / passive only)
|
|
92
|
+
|
|
93
|
+
The capture lanes. In-bounds **only** for the user's own session, the user's own first-party clients, or
|
|
94
|
+
passive sources. **Surface these; never run them for the user.**
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
brew install mitmproxy websocat grpcurl subfinder tlsx testssl jadx apktool
|
|
98
|
+
npm i -g chrome-remote-interface get-graphql-schema
|
|
99
|
+
# one-time, USER-RUN: expose a CDP port on the user's own Chrome (closes the WS-frame blind spot)
|
|
100
|
+
# open -na "Google Chrome" --args --remote-debugging-port=9222
|
|
101
|
+
# mobile-only, the user's own device + explicit opt-in: uv tool install frida-tools
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
| Tool | Unlocks | Ethics note |
|
|
105
|
+
| --- | --- | --- |
|
|
106
|
+
| **mitmproxy** | decode full HTTPS + WebSocket flows of any **first-party** client (CLI/desktop/mobile/SDK) — the `wire-capture` rung-3 keystone (`ingestion.md` §7.1) | user installs the CA and routes *their own* client only |
|
|
107
|
+
| **chrome-remote-interface** | raw CDP → `Network.webSocketFrameReceived`/`Created`, SSE, full bodies — **captures the on-load WS frames the browser MCP's HTTP-only `read_network_requests` drops** | the user's own logged-in session |
|
|
108
|
+
| **websocat** | curl-for-WebSockets: connect / frame / replay / hexdump a realtime endpoint outside the browser | first-party endpoint |
|
|
109
|
+
| **grpcurl** | list services + invoke RPCs via gRPC server reflection → readable op/message catalog | first-party endpoint |
|
|
110
|
+
| **get-graphql-schema** | introspect a first-party GraphQL endpoint → full SDL | first-party endpoint |
|
|
111
|
+
| **subfinder · tlsx · testssl.sh** | passive CT/DNS subdomain breadth + structured TLS posture (vs hand-rolled crt.sh + openssl text) | passive aggregation; respect rate limits |
|
|
112
|
+
| **jadx · apktool** | decompile Android `.apk`/`.dex` → Java + decode `AndroidManifest`/resources (capability map) | public binary; read the EULA first (`ingestion.md` §7.2) |
|
|
113
|
+
| **frida-tools** | cert-pinning bypass to capture **the user's own** mobile client's wire | the user's own device + explicit opt-in only |
|
|
114
|
+
|
|
115
|
+
> **The single biggest unlock for a realtime target** is `chrome-remote-interface` + a Chrome
|
|
116
|
+
> `--remote-debugging-port` + `websocat` + `mitmproxy` together: they give `session`/`wire-capture` a real
|
|
117
|
+
> WebSocket/SSE capture path instead of a client-side-remount workaround. The browser MCP's
|
|
118
|
+
> `read_network_requests` is **HTTP-only** — that is *why* a realtime tap comes back empty until the remount
|
|
119
|
+
> trick (`tradecraft.md` gotcha #13, `ingestion.md` §9.4). A CDP port removes the injection-timing problem
|
|
120
|
+
> entirely rather than working around it.
|
|
121
|
+
>
|
|
122
|
+
> (Origin: Emergent — with no CDP port open, the run fell back to in-page taps. That worked, but a
|
|
123
|
+
> post-boot `window.WebSocket` wrapper is structurally blind to a library socket opened at boot, and the
|
|
124
|
+
> prior run published a false negative because of it.)
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## AVOID — never recommend (this is the bright line)
|
|
129
|
+
|
|
130
|
+
Out-of-bounds for a spec-writing / competitive-analysis harness. Listed so they are not added by reflex:
|
|
131
|
+
|
|
132
|
+
- **`nmap`, `masscan`, `naabu`** — active port sweeps / service-version probing of remote hosts.
|
|
133
|
+
- **`nuclei`** — template-based remote vulnerability scanning.
|
|
134
|
+
- **`sqlmap`** — injection / exploitation.
|
|
135
|
+
- **`amass` (active mode)** — active DNS brute-forcing. Its *passive* mode overlaps subfinder; prefer subfinder.
|
|
136
|
+
- **`mitmproxy` or `frida` against a third party**, **`socat` as a public relay** — intercepting traffic the
|
|
137
|
+
user does not own is the line this harness will not cross.
|
|
138
|
+
|
|
139
|
+
These stay absent **by design**. The recon ethics depend on it, and a tool absent from the host is a
|
|
140
|
+
stronger guarantee than a rule telling the agent not to use it.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Per-dimension gap map (what a bare host cannot do)
|
|
145
|
+
|
|
146
|
+
| Dimension | Biggest capability gap on a bare host | Top addition(s) |
|
|
147
|
+
| --- | --- | --- |
|
|
148
|
+
| **codebase** | no LOC census, no AST search; the host's BSD ctags is broken for this | scc · ast-grep · universal-ctags (+ fd) |
|
|
149
|
+
| **docs** | no HTML/MDX→md, no JS render → client-rendered docs come back empty | pandoc · trafilatura · playwright |
|
|
150
|
+
| **packages** | the real `.d.ts`/exports surface is string-mined, not parsed | typescript + attw + publint · griffe (Py) · yq |
|
|
151
|
+
| **api** | can fetch a spec but cannot lint / diff / introspect / exercise it | spectral · oasdiff · get-graphql-schema · grpcurl · websocat |
|
|
152
|
+
| **website** | no JS render, no clean table/text extraction → misses pricing and feature grids | trafilatura · htmlq · playwright |
|
|
153
|
+
| **community** | no clean changelog/blog→text, no RSS→JSON | trafilatura · pandoc · dasel |
|
|
154
|
+
| **session** | **structurally blind to WebSocket/SSE frames** (HTTP-only MCP, no CDP port) | chrome-remote-interface · mitmproxy · websocat |
|
|
155
|
+
| **deployed-client-bundle** | a minified/obfuscated blob stays opaque; no source-map reassembly | webcrack · source-map-explorer · ast-grep · gron |
|
|
156
|
+
| **infra-backend-fingerprint** | shallow CT coverage, TLS posture by hand, sub-processor pages scraped raw | subfinder · tlsx · trafilatura (passive only) |
|
|
157
|
+
| **wire-capture** | the keystone (mitmproxy) plus every non-browser replay/decode client is absent | mitmproxy · websocat · grpcurl |
|
|
158
|
+
| **distribution-artifacts** | only CRX/Mach-O readable; no Electron or Android decoders | @electron/asar · jadx · apktool · webcrack · sevenzip |
|
|
159
|
+
| **external-reputation / hiring-intel** | bot-walled review and ATS pages return 403 to plain fetch | playwright (own headless instance) · trafilatura · dasel |
|
|
160
|
+
| **cross-cutting (synthesis)** | no structured diff for the N-way seam reconciliation; no out-of-band WS capture | gron · oasdiff · chrome-remote-interface · yq |
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Known install caveats (each of these has actually bitten)
|
|
165
|
+
|
|
166
|
+
- **`webcrack` needs Node 22.** Its hard dependency `isolated-vm` is a native (node-gyp) addon that fails to
|
|
167
|
+
build against Node 26, and `--omit=optional` does not help — the dep is non-optional. Run it under a Node
|
|
168
|
+
22 LTS shim (`npx -y webcrack@latest …` in a Node-22 env), or lean on the already-clean
|
|
169
|
+
`source-map-explorer` (the preferred `source-map-reassembly` path whenever the SPA ships `.map` files)
|
|
170
|
+
plus `ast-grep`. De-obfuscating a **no-maps, minified** bundle is the only gap this leaves.
|
|
171
|
+
- **`chrome-remote-interface` is a library, not a CLI.** It installs as a Node module and is invoked from a
|
|
172
|
+
script, not a shell binary. **Detect it with `npm ls -g chrome-remote-interface`, not `command -v`** — a
|
|
173
|
+
`command -v` probe reports it missing when it is installed, and the run then wrongly records a capability
|
|
174
|
+
gap.
|
|
175
|
+
- **`pipx` is typically absent; use `uv tool install`** for Python CLIs (uv is present). Simpler than adding pipx.
|
|
176
|
+
- **`playwright` is clean** — the user's own headless instance. It unblocks JS-rendered docs and marketing
|
|
177
|
+
for `docs` + `website` **without contending for the gated `session` Chrome**, which matters because the
|
|
178
|
+
browser is a singleton (`ingestion.md` §7 rule 9).
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## How Step 0 uses this reference
|
|
183
|
+
|
|
184
|
+
Once per corpus folder, before Mode 1. If `.claude/discover-tooling.json` exists, tooling was already
|
|
185
|
+
reviewed here — **skip straight to Mode 1 and never re-prompt.** If it is absent:
|
|
186
|
+
|
|
187
|
+
1. Read this file — the tier lists, the classification, the AVOID list.
|
|
188
|
+
2. **Detect before recommending** — `command -v <tool>` per tool, and `npm ls -g <pkg>` for the Node
|
|
189
|
+
libraries that ship no binary (see the caveat above). Detection only; nothing is installed at this step.
|
|
190
|
+
3. **Recommend only what is missing**, grouped **clean** (offer the Tier-1 one-paste block) vs **gated**
|
|
191
|
+
(surface the command, never run it). Never suggest anything on the AVOID list. If nothing is missing,
|
|
192
|
+
say so in one line.
|
|
193
|
+
4. Write the state file so the harness never asks again in this folder:
|
|
194
|
+
```json
|
|
195
|
+
{ "checked": "<ISO date>", "present": ["gron", "pandoc"], "missing": ["mitmproxy"], "recommended": true }
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The state file is **host-local** (tools live on the machine, not the repo) — gitignore it in a shared
|
|
199
|
+
folder; delete it to force a fresh check after installing a batch. **This step never blocks a run.**
|
|
200
|
+
|
|
201
|
+
> **Maintenance loop.** When a run hits a capability wall — a format nothing on the host can read, a
|
|
202
|
+
> capture lane that needed a tool that was not listed, an install that failed in a new way — fold it back
|
|
203
|
+
> in here: the tool into the right tier with its ethics class, or the failure into the caveats. This file
|
|
204
|
+
> only stays useful if every new wall is recorded (the same loop `ingestion.md` §7.0 carries for redaction
|
|
205
|
+
> patterns).
|
|
@@ -69,12 +69,25 @@ The runnable in-page instrumentation `session`/`wire-capture` install to mine th
|
|
|
69
69
|
return origOpen.apply(this, arguments);
|
|
70
70
|
};
|
|
71
71
|
XMLHttpRequest.prototype.send = function (body) {
|
|
72
|
-
|
|
72
|
+
const rec = {
|
|
73
73
|
kind: "xhr",
|
|
74
74
|
url: this.__url,
|
|
75
75
|
method: this.__method,
|
|
76
|
-
|
|
76
|
+
// clamp MATCHES the fetch tap (8000) — see the clamp-parity note below
|
|
77
|
+
body: typeof body === "string" ? body.slice(0, 8000) : "[non-string]",
|
|
77
78
|
at: Date.now(),
|
|
79
|
+
};
|
|
80
|
+
window.__fetchTap.push(rec);
|
|
81
|
+
this.addEventListener("load", () => {
|
|
82
|
+
try {
|
|
83
|
+
rec.status = this.status;
|
|
84
|
+
rec.respCt = (this.getResponseHeader("content-type") || "").split(";")[0];
|
|
85
|
+
const t = this.responseText;
|
|
86
|
+
if (typeof t === "string") {
|
|
87
|
+
rec.respLen = t.length;
|
|
88
|
+
rec.resp = t.slice(0, 12000);
|
|
89
|
+
}
|
|
90
|
+
} catch (_) {}
|
|
78
91
|
});
|
|
79
92
|
return origSend.apply(this, arguments);
|
|
80
93
|
};
|
|
@@ -87,6 +100,105 @@ The runnable in-page instrumentation `session`/`wire-capture` install to mine th
|
|
|
87
100
|
|
|
88
101
|
---
|
|
89
102
|
|
|
103
|
+
### 1b. The clipboard-to-local channel (unlimited size, bypasses BOTH content filters)
|
|
104
|
+
|
|
105
|
+
**When to use:** any captured body that exceeds the `javascript_tool` display cap (as low as ~1.1 KB) or
|
|
106
|
+
that contains blocklisted shapes (query strings, long base64, many UUIDs) which would return
|
|
107
|
+
`[BLOCKED: Cookie/query string data]`. Paging a 90 KB body at ~1 KB a slice costs ~90 round trips; this
|
|
108
|
+
costs one. It is also immune to Chrome's silent multi-download block.
|
|
109
|
+
|
|
110
|
+
**Requires document focus** — click the page once before calling it, or `execCommand('copy')` returns `false`.
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
window.__CP = (obj) => {
|
|
114
|
+
const ta = document.createElement("textarea");
|
|
115
|
+
ta.value = typeof obj === "string" ? obj : JSON.stringify(obj, null, 1);
|
|
116
|
+
ta.style.cssText = "position:fixed;left:0;top:0;width:100px;height:60px;opacity:0.01;z-index:2147483647";
|
|
117
|
+
document.body.appendChild(ta); ta.focus(); ta.select();
|
|
118
|
+
ta.setSelectionRange(0, ta.value.length);
|
|
119
|
+
const ok = document.execCommand("copy"); ta.remove();
|
|
120
|
+
return "copied=" + ok + " len=" + ta.value.length; // returns only a length — never the payload
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Then read it locally — the payload never passes through the model's context or the content filter:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
pbpaste > capture.json # macOS
|
|
128
|
+
xclip -o > capture.json # Linux
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Scrub before copying**, not after: build the object with `__SCRUB` first, then `window.__CP(pack)`. The
|
|
132
|
+
clipboard is local, so this stays inside the no-exfiltration rule (ingestion §7 rule 7).
|
|
133
|
+
|
|
134
|
+
> (Origin: Emergent, second run — the prior run recorded router model-ids as "masked `[variant]`" and 6 of
|
|
135
|
+
> 77 agent rows as display-cap truncated. Both were artifacts of the *display* layer, not the capture: the
|
|
136
|
+
> same values came back verbatim through the clipboard channel. **The display layer is not the capture
|
|
137
|
+
> layer.**)
|
|
138
|
+
|
|
139
|
+
> **Clamp parity is load-bearing — the XHR branch must match the fetch branch (Mode-5, Emergent).** Many
|
|
140
|
+
> SPAs call their own API through **axios/XHR, not `fetch`** (Emergent does). An XHR branch clamped smaller
|
|
141
|
+
> than the fetch branch silently truncates exactly the payload you care about most — a real run lost the
|
|
142
|
+
> primary write body (`POST /jobs/v0/submit-queue/`) to a 600-char clamp and had to re-wrap mid-capture.
|
|
143
|
+
> Keep both at 8000 (request) / 12000 (response).
|
|
144
|
+
>
|
|
145
|
+
> **Re-wrapping XHR mid-run is safe; re-wrapping `fetch` is not.** The double-wrap hazard is specific to
|
|
146
|
+
> `fetch` + `response.clone()` (gotcha §2.9): a second body interceptor breaks the inner wrapper's clone.
|
|
147
|
+
> XHR has no clone semantics, so installing a second, wider XHR wrapper (under a fresh sentinel such as
|
|
148
|
+
> `window.__xhr2`) is a legitimate mid-run recovery when you discover the first clamp was too small.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
### 1c. Pre-shutter redaction overlay (run before EVERY screenshot)
|
|
153
|
+
|
|
154
|
+
**When to use:** any `save_to_disk` capture (`ingestion.md` §5.5). Redaction must happen **in the DOM before
|
|
155
|
+
the shutter** — a leaked pixel is undetectable afterwards, because the §7.0 4c literal sweep is text-only and
|
|
156
|
+
`grep` cannot read a PNG.
|
|
157
|
+
|
|
158
|
+
Idempotent; re-run it after every navigation and before every capture, since the elements needing redaction
|
|
159
|
+
change as you move. Extend `PII` with the current session's own identifiers at run time (never commit them).
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
(() => {
|
|
163
|
+
if (!document.getElementById("__redact_style")) {
|
|
164
|
+
const st = document.createElement("style");
|
|
165
|
+
st.id = "__redact_style";
|
|
166
|
+
st.textContent = `.__redacted{background:#111 !important;color:transparent !important;
|
|
167
|
+
border-radius:3px !important;text-shadow:none !important}
|
|
168
|
+
.__redacted *{visibility:hidden !important}
|
|
169
|
+
.__blurred{filter:blur(14px) !important}`;
|
|
170
|
+
document.head.appendChild(st);
|
|
171
|
+
}
|
|
172
|
+
// 1. account identity — extend per run (name / email / org / workspace slug)
|
|
173
|
+
const PII = /REPLACE_WITH_RUN_IDENTIFIERS/i;
|
|
174
|
+
let n = 0;
|
|
175
|
+
document.querySelectorAll("body *").forEach(el => {
|
|
176
|
+
if (el.children.length === 0 && el.textContent && PII.test(el.textContent)) {
|
|
177
|
+
el.classList.add("__redacted"); n++;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
// 2. THIRD-PARTY SURFACES — the corner-of-the-frame trap. Blur wholesale; do not enumerate their text.
|
|
181
|
+
const THIRD_PARTY = [
|
|
182
|
+
'[class*="intercom" i]','[id*="intercom" i]','iframe[src*="intercom"]',
|
|
183
|
+
'[class*="crisp" i]','[id*="crisp" i]','[class*="drift" i]','[class*="zendesk" i]',
|
|
184
|
+
'[class*="beacon" i]','[class*="helpscout" i]','[class*="freshchat" i]',
|
|
185
|
+
'[class*="toast" i]','[class*="notification" i]','[role="alert"]','[role="status"]',
|
|
186
|
+
'[class*="presence" i]','[class*="avatar-group" i]','[class*="online" i]'
|
|
187
|
+
];
|
|
188
|
+
document.querySelectorAll(THIRD_PARTY.join(",")).forEach(el => { el.classList.add("__blurred"); n++; });
|
|
189
|
+
// 3. avatars anywhere
|
|
190
|
+
document.querySelectorAll('img[src*="avatar"],img[src*="googleusercontent"],[class*="avatar" i] img')
|
|
191
|
+
.forEach(el => { el.classList.add("__blurred"); n++; });
|
|
192
|
+
return "redacted_nodes=" + n;
|
|
193
|
+
})()
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
> **Then LOOK at the saved image before asserting it is clean.** The overlay handles what you predicted;
|
|
197
|
+
> the frame may hold what you did not. Record `redacted: <what>` in `screens/_index.md` — never a blanket
|
|
198
|
+
> "no PII in the pixels" claim you did not verify by reading the file (`ingestion.md` §5.5 origin note).
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
90
202
|
### 2. WebSocket constructor wrapper (with binary-frame signature handling)
|
|
91
203
|
|
|
92
204
|
**When to use:** any realtime/collab/streaming layer carried over WebSocket. Catches only sockets created **after** install — a library already initialised at page-boot (Pusher/Ably) is invisible here; use tap 4 instead. For an on-load socket, client-side-nav to a fresh room so a new socket is constructed through your wrapper (ingestion §9.4).
|
|
@@ -286,6 +398,8 @@ The Chrome-MCP browser-driving layer has ~27 recurring failure modes; every one
|
|
|
286
398
|
27. **Probe Bearer vs cookie vs raw-token explicitly** — a JS-readable token cookie does NOT imply cookie-auth; on Creatify and Omneky the API rejects the raw cookie (`credentials:'include'` → `Failed to fetch` under `ACAO:*`) and accepts only an explicit `Authorization: Bearer` → probe all three (Bearer header / cookie / raw-token) to pin the real header model before mining. _(§2.26)_
|
|
287
399
|
28. **Dynamic/runtime-constructed realtime hub URLs can't be recovered by bundle regex** — AdCreative's SignalR hub path is built at runtime in `initializeHub` with no static literal → record realtime _presence_ + transport as confirmed, the URL + message contract as open (needs Pass 2). Don't report "no realtime" just because grep found no socket URL. _(§2.27 — AdCreative)_
|
|
288
400
|
|
|
401
|
+
29. **`javascript_tool` does NOT await promises — an `async` IIFE returns `{}`** — the tool stringifies the pending Promise, so `(async () => { const r = await fetch(...); return r; })()` yields a bare `{}` that looks identical to a content block or an empty result → **never `await` at the top level.** Use fire-and-forget + poll: fire the requests storing results on `window.__X`, return a literal like `"fired"`, then poll `window.__X` in a second call. Three captures were silently lost to this before it was diagnosed. _(§2.28 — Emergent, second run)_
|
|
402
|
+
|
|
289
403
|
### The escape snippets (paste verbatim)
|
|
290
404
|
|
|
291
405
|
**Fire-and-forget poll — CDP `Runtime.evaluate` times out at ~45s; a fetch that holds the connection open longer (long-poll, slow stream) must be fired, then polled (§1.10):**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nurix/etna",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.42",
|
|
4
4
|
"description": "Bootstrap a project with a Nurix harness — copies the harness's CLAUDE.md, skills, agents, rules, and hooks directly into the project from a bundled payload (no marketplace fetch, no network).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,5 +41,5 @@
|
|
|
41
41
|
"agent",
|
|
42
42
|
"plugin"
|
|
43
43
|
],
|
|
44
|
-
"etnaFingerprint": "
|
|
44
|
+
"etnaFingerprint": "e47ee7aac58b"
|
|
45
45
|
}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
# Tooling recommendations — what to install to improve capture & synthesis
|
|
2
|
-
|
|
3
|
-
> The `discover` harness reads this file on **first run** (Step 0 in [`CLAUDE.md`](CLAUDE.md)): it detects which of these tools are already on the host, recommends only the **missing** ones (the gated ones only with the user in the loop), and records the result in `.claude/discover-tooling.json` so it never re-prompts in this folder. Nothing here is installed automatically — the harness recommends, the user decides.
|
|
4
|
-
|
|
5
|
-
The host almost always arrives strong on languages and package managers (node, pnpm, python, uv, cargo, gh, jq, ripgrep) but **bare on capture/analysis tooling** — the tools below are what turn a shallow capture into a deep one.
|
|
6
|
-
|
|
7
|
-
## How to read this
|
|
8
|
-
|
|
9
|
-
- **clean** — passive / local-only; always in-bounds. Install freely.
|
|
10
|
-
- **gated** — fine, but only **first-party / user-owned / opt-in** (the user installs the CA, routes _their own_ client, owns the keys). mitmproxy, websocat, CDP, subfinder/tlsx, frida on the user's own device.
|
|
11
|
-
- **avoid** — crosses the harness bright line (active scanning / exploitation / third-party packet capture). Listed so they're _not_ added by reflex.
|
|
12
|
-
|
|
13
|
-
The harness is for **spec-writing / integration / competitive analysis** — passive or first-party only, respect TOS/robots. No tool below changes that; the gated ones just need the user in the loop.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Tier 1 — install now (clean, broad leverage)
|
|
18
|
-
|
|
19
|
-
The highest-consensus, always-in-bounds tools. One paste:
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
# Homebrew (CLI analysis + data-wrangling + web→text)
|
|
23
|
-
brew install gron pandoc ast-grep scc universal-ctags oasdiff fd yq dasel monolith tokei sevenzip
|
|
24
|
-
# Rust
|
|
25
|
-
cargo install htmlq
|
|
26
|
-
# Python (via uv — pipx is absent; uv tool install is the path)
|
|
27
|
-
uv tool install trafilatura && uv tool install griffe
|
|
28
|
-
# Node global (JS/TS surface + bundle de-obfuscation + spec lint)
|
|
29
|
-
npm i -g webcrack source-map-explorer @arethetypeswrong/cli publint @electron/asar typescript @stoplight/spectral-cli
|
|
30
|
-
# Headless browser for JS-rendered docs/marketing (clean — the user's own headless instance)
|
|
31
|
-
npm i -g playwright && npx playwright install chromium
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
| Tool | Unlocks | Dimensions served |
|
|
35
|
-
| --- | --- | --- |
|
|
36
|
-
| **gron** | flatten any JSON → greppable/diffable `path = value` (specs, captures, manifests, HAR, configs) | **7** (codebase, api, session, bundle, infra, wire, cross-cutting) |
|
|
37
|
-
| **trafilatura** | boilerplate-stripped main-content + dates from HTML → clean markdown; feed/sitemap discovery | **4** (docs, website, community, infra) |
|
|
38
|
-
| **pandoc** | deterministic HTML→GFM (headings, tables, code fences) at scale | 3 (docs, website, community) |
|
|
39
|
-
| **ast-grep** | tree-sitter structural search/count across many files+langs (call sites, routes, exports) — catches what regex misses | 2 (codebase, bundle) |
|
|
40
|
-
| **oasdiff** | structured breaking-vs-non-breaking OpenAPI diff — makes the published-vs-app-own diff machine-checked | 2 (api, cross-cutting) |
|
|
41
|
-
| **webcrack** | un-minify / un-webpack / de-obfuscate SPA & Electron JS into readable modules | 2 (bundle, distribution) |
|
|
42
|
-
| **scc** | instant LOC-by-language + complexity census of a fresh clone (JSON) — the codebase opening move | 1 (codebase) |
|
|
43
|
-
| **universal-ctags** | real multi-lang JSON symbol index (the host's BSD ctags is broken for this) | 1 (codebase) |
|
|
44
|
-
| **htmlq** | surgical CSS-selector extraction (pricing cards, feature tables) instead of regex | 1 (website) |
|
|
45
|
-
| **source-map-explorer** | reassemble shipped `.map` → original `src/` tree (the `source-map-reassembly` method) | 1 (bundle) |
|
|
46
|
-
| **spectral** | validate/resolve an OpenAPI/AsyncAPI spec → structured endpoint/security/$ref catalog | 1 (api) |
|
|
47
|
-
| **griffe** | extract a PyPI package's full public API (the `.d.ts`-equivalent for Python) | 1 (packages) |
|
|
48
|
-
| **@arethetypeswrong/cli + typescript + publint** | parse the real `.d.ts`/exports type graph + grade the publish surface (verbatim, not string-mined) | 1 (packages) |
|
|
49
|
-
| **@electron/asar** | extract Electron `app.asar` → JS module tree (the distribution step the playbook names but can't run) | 1 (distribution) |
|
|
50
|
-
| **fd / yq / dasel / monolith / tokei / sevenzip** | gitignore-aware file discovery · YAML/XML/TOML query · RSS/XML→JSON · single-file page archive · LOC cross-check · multi-format extract | several |
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
## Tier 2 — install when a run needs them (gated: first-party / passive-care)
|
|
55
|
-
|
|
56
|
-
The capture lanes. In-bounds **only** for the user's own session / first-party clients / passive sources.
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
brew install mitmproxy websocat grpcurl subfinder tlsx testssl jadx apktool
|
|
60
|
-
npm i -g chrome-remote-interface get-graphql-schema
|
|
61
|
-
# one-time, user-run: expose a CDP port on the user's own Chrome (closes the WS-frame blind spot)
|
|
62
|
-
# open -na "Google Chrome" --args --remote-debugging-port=9222
|
|
63
|
-
# mobile-only, the user's own device + opt-in: uv tool install frida-tools
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
| Tool | Unlocks | Ethics note |
|
|
67
|
-
| --- | --- | --- |
|
|
68
|
-
| **mitmproxy** | decode full HTTPS + WebSocket flows of any **first-party** client (CLI/desktop/mobile/SDK) — wire-capture rung 3 keystone | user installs CA + routes _their own_ client only |
|
|
69
|
-
| **chrome-remote-interface** | raw CDP client → `Network.webSocketFrameReceived`/`Created`, SSE, full bodies — **captures the on-load WS frames the browser MCP's HTTP-only `read_network_requests` drops** | the user's own logged-in session |
|
|
70
|
-
| **websocat** | curl-for-WebSockets: connect/frame/replay/hexdump a realtime endpoint outside the browser | first-party endpoint |
|
|
71
|
-
| **grpcurl** | list services + invoke RPCs via gRPC server reflection → readable op/message catalog | first-party endpoint |
|
|
72
|
-
| **get-graphql-schema** | introspect a first-party GraphQL endpoint → full SDL | first-party endpoint |
|
|
73
|
-
| **subfinder / tlsx / testssl.sh** | passive CT/DNS subdomain breadth + structured TLS posture (vs hand-rolled crt.sh + openssl text) | passive aggregation; respect rate limits |
|
|
74
|
-
| **jadx / apktool** | decompile Android `.apk`/`.dex` → Java + decode `AndroidManifest`/resources (capability map) | public binary, read EULA |
|
|
75
|
-
| **frida-tools** | cert-pinning bypass to capture **the user's own** mobile client's wire | the user's own device + opt-in only |
|
|
76
|
-
|
|
77
|
-
**The single biggest unlock** for realtime targets: `chrome-remote-interface` + a Chrome `--remote-debugging-port` + `websocat` + `mitmproxy` together give `session`/`wire-capture` a real WebSocket/SSE capture path, instead of a client-side-remount workaround. The browser-MCP `read_network_requests` is HTTP-only — that's _why_ a realtime tap comes back empty until the remount trick.
|
|
78
|
-
|
|
79
|
-
---
|
|
80
|
-
|
|
81
|
-
## AVOID — do NOT install (crosses the harness bright line)
|
|
82
|
-
|
|
83
|
-
Out-of-bounds for a spec-writing/competitive-analysis harness — listed so they aren't added by reflex:
|
|
84
|
-
|
|
85
|
-
- **`nmap`, `masscan`, `naabu`** — active port sweeps / service-version probing of remote hosts.
|
|
86
|
-
- **`nuclei`** — template-based remote vuln scanning.
|
|
87
|
-
- **`sqlmap`** — injection/exploitation.
|
|
88
|
-
- **`amass` (active mode)** — active DNS brute-forcing (its _passive_ mode overlaps subfinder; prefer subfinder).
|
|
89
|
-
- **third-party `mitmproxy`, `frida` on devices the user doesn't own, `socat` as a public relay** — third-party traffic interception is the line this harness will not cross.
|
|
90
|
-
|
|
91
|
-
These stay absent by design; the recon ethics depend on it.
|
|
92
|
-
|
|
93
|
-
---
|
|
94
|
-
|
|
95
|
-
## Per-dimension view (gap → top pick)
|
|
96
|
-
|
|
97
|
-
| Dimension | Biggest capability gap on a bare host | Top addition(s) |
|
|
98
|
-
| --- | --- | --- |
|
|
99
|
-
| **codebase** | no LOC census, no AST search; BSD ctags is broken | scc · ast-grep · universal-ctags (+ fd) |
|
|
100
|
-
| **docs** | no HTML/MDX→md, no JS-render → client-rendered docs come back empty | pandoc · trafilatura · playwright |
|
|
101
|
-
| **packages** | the real `.d.ts`/exports surface is string-mined, not parsed | typescript+attw+publint · griffe (Py) · yq |
|
|
102
|
-
| **api** | can fetch a spec but can't lint/diff/introspect/exercise it | spectral · oasdiff · get-graphql-schema · grpcurl · websocat |
|
|
103
|
-
| **website** | no JS-render, no clean table/text extraction → misses pricing/feature grids | trafilatura · htmlq · playwright |
|
|
104
|
-
| **community** | no clean changelog/blog→text, no RSS→JSON | trafilatura · pandoc · dasel |
|
|
105
|
-
| **session** | **structurally blind to WebSocket/SSE frames** (HTTP-only MCP, no CDP port) | chrome-remote-interface · mitmproxy · websocat |
|
|
106
|
-
| **deployed-client-bundle** | minified/obfuscated blob stays opaque; no source-map reassembly | webcrack · source-map-explorer · ast-grep · gron |
|
|
107
|
-
| **infra-backend-fingerprint** | shallow CT coverage, TLS posture by hand, sub-processor pages scraped raw | subfinder · tlsx · trafilatura (passive only) |
|
|
108
|
-
| **wire-capture** | keystone mitmproxy + all non-browser replay/decode clients absent | mitmproxy · websocat · grpcurl |
|
|
109
|
-
| **distribution-artifacts** | only CRX/Mach-O readable; no Electron/Android decoders | @electron/asar · jadx · apktool · webcrack · sevenzip |
|
|
110
|
-
| **cross-cutting (synthesis)** | no structured diff for the N-way seam reconciliation; no out-of-band WS capture | gron · oasdiff · chrome-remote-interface · yq |
|
|
111
|
-
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
## Known install caveats
|
|
115
|
-
|
|
116
|
-
- **`webcrack` needs Node 22.** Its hard dependency `isolated-vm` is a native (node-gyp) addon that fails to build against Node 26 (and `--omit=optional` doesn't help — the dep is non-optional). If it won't install, run it under a Node 22 LTS shim (`npx -y webcrack@latest …` in a Node-22 env), or lean on the already-clean `source-map-explorer` (the preferred `source-map-reassembly` path whenever the SPA ships `.map` files) + `ast-grep`. De-obfuscation of a _no-maps, minified_ bundle is the only gap this leaves.
|
|
117
|
-
- **`chrome-remote-interface` is a library, not a CLI** — it installs as a Node module and is invoked from a script, not a shell binary. Detect it with `npm ls -g chrome-remote-interface`, not `command -v`.
|
|
118
|
-
- **`pipx` is typically absent; use `uv tool install`** for Python CLIs (uv is present) — simpler than adding pipx.
|
|
119
|
-
- **`playwright` is clean** (the user's own headless instance) and unblocks JS-rendered docs/marketing for `docs`+`website` without contending for the gated `session` Chrome.
|