Releases: dtduc-git/jevnav
Release list
v0.2.0: make the MCP authority boundary explicit and honest
0.2.0: make the MCP authority boundary explicit and honest
An external review of the MCP surface (and the doc claims around it) found
that the gate only covered browse/goal while the README implied the whole
tool set, direct primitives left no trace, upload_files could read any host
path, goto accepted file://, and lighthouse ran an unpinned npx with a
caller-controlled argv.
- every acting MCP tool writes a kind:"action" record to the same trace as
decisions (typed values, JS expressions and single-character keys masked);
read_trace skips action records, read_actions exposes them, SPEC documents
the record - fill_form/upload_files intent resolution goes through the same gate as
browse (upload's target choice matches the element only, since "upload"
itself is on the risky list) - --file-root (default cwd) bounds upload reads and artifact writes, and
refuses a default root that is / or the home directory; --allow-file-urls
opts file:// back in for goto/new_page - lighthouse: http(s) only, pinned 13.5.0, URL validated before npx
- annotations: screenshot/heap_snapshot/trace_stop/dialog_policy destructive
- cli: --no-eval/--max-candidates now actually reach serve() (dropped before)
- README/SECURITY/SPEC: gate scope, traced vs replayed, readers not traced,
--no-trace, masking limits - tests: 239 (action records, intent gate + decision steps, file-root and
symlink escapes, scheme guards, replay --execute on an intent step,
single-char key masking)
Reviewed by oc-review over three passes (last pass only doc/test nits).
v0.1.15: fix network over MCP, stable request ids, sharper tool docs
0.1.15: fix network over MCP, stable request ids, sharper tool docs
Glama scored the v0.1.14 tool set B 3.4/5; outline was the only C (2.9)
and the server score weights the minimum tool at 40%, so this release
rewrites the terse descriptions and fixes the two real bugs they exposed.
- network_detail ran response.text() from the MCP handler thread: every
call over MCP raised a greenlet error. It now goes through on_page like
every other browser operation. - network entries carried live Playwright request/response objects, so the
MCP layer's json.dumps crashed. network_tail projects serializable
fields and adds an id; network_detail looks the request up by that id
(a ring-buffer position shifts once 200 entries are recorded). - the id counter is shared across tabs, a tab's recorder is reused when it
is selected again (no double listeners, history kept), and closed tabs
are pruned. - fill_form check accepts the true-like strings a model sends; outline,
console, goto, network, network_detail, fill_form and press_key now
document what they actually return and change. - tests: 220 (id round-trip over MCP, ring overflow, tab recorder reuse,
check values, network json path).
Reviewed by oc-review over three passes (final pass only style nits).
v0.1.14: MCP tool annotations and honest side-effect disclosures
0.1.14: MCP tool annotations and honest side-effect disclosures
Glama's TDQS reads the schema, not just the prose: every tool now carries
MCP annotations, and the nine tools it scored C got descriptions that say
what they do to the page, the disk and later calls.
- annotations on all 33 tools: readOnlyHint for observation, and
destructiveHint=true where a call can overwrite or remove state
(browse, goal, close_page, fill_form, press_key, drag, upload_files,
read_js); idempotentHint=false where a repeat is not a no-op
(screenshot/heap_snapshot write timestamped files, scroll is relative,
trace_start/stop change session state) - read_js is documented as arbitrary JavaScript, not observation: its
annotation set is no longer read-only - lighthouse: readOnly=false (npx fetches and runs a second Chrome), and
it resolves the current URL through on_page instead of crashing when no
browser has started yet - dialog_policy, upload_files, route, emulate, select_page, resize, drag,
heap_snapshot: descriptions now state persistence, replacement and
error conditions - tests pin the full 33-tool annotation table over the real stdio protocol
jevnav v0.1.13
jevnav 0.1.13 — dependency security patch.
MCP Marketplace flagged five High CVEs in the mcp Python SDK, all of them in
versions the old mcp>=1.2 floor allowed. The floor is now mcp>=1.28.1, the
highest patched version among the advisories:
| Advisory | CVE | Affected | Patched |
|---|---|---|---|
| GHSA-3qhf-m339-9g5v | CVE-2025-53366 | < 1.9.4 | 1.9.4 |
| GHSA-j975-95f5-7wqh | CVE-2025-53365 | < 1.10.0 | 1.10.0 |
| GHSA-9h52-p55h-vw2f | CVE-2025-66416 | < 1.23.0 | 1.23.0 |
| GHSA-jpw9-pfvf-9f58 | CVE-2026-52869 | <= 1.27.1 | 1.27.2 |
| GHSA-vj7q-gjh5-988w | CVE-2026-59950 | < 1.28.1 | 1.28.1 |
No API changes. The lock stays on mcp 2.2.0 (full suite green); the new floor
was checked by hand at mcp==1.28.1 (MCP test files pass). server.json is
bumped alongside the package and a test now keeps the two versions in sync.
jevnav v0.1.12
jevnav 0.1.12 — the pytest plugin becomes the front door.
jev.expect(...)is now recorded on the trace as a verify block, so
replay --executechecks the outcome instead of only re-running the actions;
an expectation step reports no drift.- A test against a local file records
file:<relative>, so its trace can be
committed and replayed on another machine.examples/pytest-interop/ships
one, and this repository's own CI replays it with the same Action users get. - The German risky pattern gained
kündigen|widerrufen— the recall gap the new
gate study found ("das abo kündigen" ran through).abmeldenstays out: it
also means "log out".
New docs: the README leads with page truth for agents (outline, styles,
diff) and keeps replay as the evidence layer; docs/why.md is the longer
argument; research/gate-study.md measures the gate — risky patterns at recall
1.00 / precision 0.65, and no wrong decision auto in 24 hard decisions, at the
cost of 61% of the correct ones going to review.
215 tests (214 pass, 1 skip).
jevnav v0.1.11
jevnav 0.1.11 — the secret pattern catches concatenated names.
APIKEY, SECRETKEY, AUTHTOKEN, GITHUB_APIKEY and STRIPE_SECRETKEY all
slipped past the token-boundary pattern and would have landed verbatim in a
committed trace. A suffix branch catches them, while PASS and AUTH stay out
of it so BYPASS_CACHE and OAUTH_REDIRECT_URI do not match. The asymmetry is
deliberate: a missed secret is silent and worse than a false positive, which at
least warns.
ORDER_SPEC moved from page.py to trace.py next to SPEC — it is written
into the run header, and the lazy import it required is gone; page.py keeps a
pointer comment.
213 tests (212 pass, 1 skip).
jevnav v0.1.10
jevnav 0.1.10 — the reorder signal gets the right ground truth.
moved compared the package version, so a trace recorded by 0.1.8 and replayed
by 0.1.9 — identical ordering code — was excused as "re-ranked" while the page
had really reordered. With a release a day, that excused almost every real
reorder. The signal is now page.ORDER_SPEC, bumped only when the shortlist
ordering (ROLE_RANK, global_order, the cap) changes, recorded in every trace as
run.order_spec. Older traces map their tool version onto it (0.1.0 → 0,
0.1.1–0.1.4 → 1, 0.1.5+ → 2); an unknown ordering never excuses movement.
The pytest plugin's secret pattern is anchored on token boundaries:
COMPASS_DIR, MONKEY_PATCH_DIR, KEYCHAIN_PATH, PASSENGER_ROOT,
AUTHOR_NAME and BYPASS_CACHE no longer look like secrets (their values were
being rewritten to value_from_env and replayed as another machine's value),
while PASSWORD123 still does.
213 tests (212 pass, 1 skip).
jevnav v0.1.9
jevnav 0.1.9 — two heuristics become facts.
moved is now excused only when the trace's run.tool differs from the running
version. The page-identical rule had swallowed real DOM reorders — same version
and the same fingerprint set means the order can only have changed on the page.
The reason names both versions, and TraceWriter records tool by default, so
every trace carries the ground truth (the pytest plugin's traces were missing
it).
The pytest plugin rewrites a literal value to value_from_env only when the
variable name looks like a secret (TOKEN, KEY, SECRET, PASSWORD, PASS,
CREDENTIAL, AUTH). A path that happens to equal $PWD or $HOME stays a
literal and says why — rewriting it used to replay as another machine's value.
210 tests (209 pass, 1 skip).
jevnav v0.1.8
jevnav 0.1.8 — replay stays honest across versions, and the plugin stops leaking.
moved compared absolute positions in the shortlist, so any change to the
extractor's ordering turned every step of an old trace into moved forever —
the two committed example traces (written by 0.1.0) had shipped that way since
0.1.1. moved now requires the candidate set to have drifted; an identical set
with a different position is ok, with the re-rank stated in the reason. Both
examples replay all-ok without being regenerated.
The pytest plugin:
- resolves
${VAR}before extraction and the decision, so a missing variable
fails the test instead of costing a Jev call first; - masks the
os.environ["TOKEN"]reflex: a value that already is an environment
value is recorded by name (value_from_env) with a warning, instead of the
literal landing in a committed trace.
SPEC.md's candidate table now documents value and rank.
208 tests (207 pass, 1 skip).
jevnav v0.1.7
jevnav 0.1.7 — the release gate, and the pytest plugin.
This release can no longer ship from a red commit: publish.yml and
mcp-registry.yml call ci.yml as a reusable workflow and wait on it. 0.1.5
reached PyPI while CI was red on the same commit; that is now structurally
impossible, not just a rule.
The pytest plugin stops putting secrets in traces and stops losing actions:
jev.fill("the password field", "${DEMO_PASSWORD}")reads the value from the
environment and the trace records only the variable name (value_from_env).
The docstring used to teach the literal-password flow.fill("")is recorded as the clear it is, andpressrecords its key; both
used to replay into aKeyError.- The fixture uses the shared
browser_session, so the browser closes even when
the test fails.
dom_index is renamed rank — it was the element's position in its frame's
shortlist, never a DOM index. Same order, honest name.
204 tests (203 pass, 1 skip).