Commit Graph

51059 Commits

Author SHA1 Message Date
Teknium
975b62663a fix(telemetry): a Desktop hand-off's update refused by its own claim counts as failed
The hand-off quits the Desktop and launches `hermes update` as the marker's
delegate. When the update lock then refuses that claim (the pre-a28a5d03a9f
whole-second creation-time bug on macOS, still present on installs that cannot
pull the fix through the button), no other updater exists: the Desktop shows
"Hermes couldn't finish updating" and the user retries. The metrics row read
outcome `refused`, which the rollup treats as neither attempt nor failure, so
~320 Desktop update failures a fortnight vanished from the failure rate.

Record that one case (marker names this process as its delegate) as `failed`;
a refusal under another update's claim stays `refused`.
2026-10-09 14:22:08 -07:00
Teknium
5d5e763780 fix(skills): inline shell snippets keep blocking community installs
Review of this PR: the inline_shell_exec downgrade assumed the runtime never
expands !`cmd` in a community hub skill. The runtime gate
(agent.skill_preprocessing._is_community_hub_skill) matches only the hub lock
entry's own directory, so a nested SKILL.md inside a community bundle is listed
as its own skill and expands when skills.inline_shell is on; so does the same
skill seen through another profile's external_dirs, or any read with an
unreadable lock. With the downgrade, such a bundle installed without --force.

inline_shell_exec stays high for community installs. The sudo/bind-all doc
notes and every other rule change are unchanged. Corpus cost: 4 of the 33
skills this PR made installable stay blocked (index 83 -> 71 instead of 68).
2026-10-09 14:03:30 -07:00
Teknium
fdb08371c7 chore: map Awshesh12's commit email for the #127414 salvage 2026-10-09 14:03:30 -07:00
Teknium
62e61882f7 fix(skills): decoded archives and curl | sudo bash stay flagged after the doc relaxations
The A/B over the index sample showed two shapes the earlier relaxations let through:
base64 -d into tar (an embedded payload the scanner never sees) and
curl ... | sudo -E bash (base never matched it; sudo_usage in Markdown was the only
finding). Both now fire.
2026-10-09 14:03:30 -07:00
Teknium
87a281e0c1 fix(skills): legitimate hub skills no longer trip Skills Guard false positives
A random sample of our own hermes-index blocked ~15% of third-party skills; most
blocks were pattern precision bugs, not threats:

- provider token shapes (AKIA, ghp_, github_pat_, sk-, sk-ant-, glpat-, BEGIN PRIVATE KEY)
  are scoped case-sensitive: the table compiles with IGNORECASE, so `[0-9A-Z]{16}`
  matched the placeholder `AKIAxxxxxxxxxxxxxxxx`
- destructive_root_rm exempts the Dockerfile package-cache cleanup
  (/var/lib/apt/lists, /var/cache/{apt,apk,yum,dnf}); traversal out of them, a later
  `/` or `/etc` operand, and `--no-preserve-root /` still fire
- base64_decode_pipe fires when the decoded bytes reach an interpreter at any later
  stage (or inside eval/source), not on `| jq` / `| grep`; plugin_guard's
  is_data_decode step-down is now redundant and is removed (it also stepped down
  `base64 -d x | sh | grep`)
- html_comment_injection needs injection phrasing, not a bare ignore/system/hidden
- setuid_setgid is critical only in executing shapes (set*id( call, cap_set[ug]id,
  chmod u+s / 4755); the bare word is a medium note
- deception_hide's guidance exemption covers diagnosis/diagnostic (diagnos\w*)
- community scans: inline_shell_exec is informational (preprocess_skill_content never
  expands it for a community hub lock entry), and sudo_usage / bind_all_interfaces in
  Markdown docs are notes; scripts keep full severity

SCANNER_VERSION bumps to skills-guard-v9 so cached verdicts rescan.
2026-10-09 14:03:30 -07:00
FlashW
887ba0c7d3 guards: base64_decode_pipe sees file/openssl decodes and every pipe stage
skills_guard's base64_decode_pipe required the pipe right after the
flag, so `base64 -d payload.b64 | sh`, `base64 --decode < p | bash`,
`base64 -di p | python3` and `openssl base64 -d | sh` produced no
finding. plugin_guard's is_data_decode stepped the finding down to
medium when the FIRST consumer was not an interpreter, so
`base64 -d | gunzip | sh` scored like a data decode into grep.

Widen the pattern to those decode forms (not `||`), and have
is_data_decode read the decode off that same pattern and require every
later pipe stage to be a non-interpreter.
2026-10-09 14:03:30 -07:00
liuhao1024
85188b47f7 fix(skills-guard): make the AWS key pattern case-sensitive
AWS access key IDs are all-caps by spec, but THREAT_PATTERNS compiles
with re.IGNORECASE, so a case-folded AKIA+16 byte collision inside a
base64-encoded asset fired one unoverridable critical and hard-blocked
the whole plugin install (#132155). Scope the pattern case-sensitive
with an inline (?-i:...) group — same technique as the hardcoded_secret
carve-out (#116221).
2026-10-09 14:03:30 -07:00
Awshesh12
b7a6fda9d1 fix(skills): curl needs an operand before the pipe-to-interpreter rules
The bare phrase `curl | sh` in documentation prose scored critical, and a
critical on a community source is a dangerous verdict that --force cannot
override, so a skill whose docs name the install method could not be installed
at all. curl with no URL fetches nothing and a real download-and-execute names
its source, so requiring one non-pipe character after the command costs no
coverage. `curl_pipe_python` carried the same optional-operand prefix.

Both scanner versions bump because cached verdicts are keyed on them: without
the bump an already-scanned skill or plugin keeps its dangerous verdict and
stays blocked.
2026-10-09 14:03:30 -07:00
Teknium
24051d9084 feat(desktop): drop the "This could run on your computer" strip above the composer
The local-setup card appeared above the input after the first finished turn
on any machine whose catalog recommends a local model, and stayed across
relaunches until dismissed. It read as a nag on every new session.

The offer keeps its quiet homes: the "Run locally" row at the top of the
model menu (until setup completes) and the handoff tour's model-pill step.
The card's state machine (armed/shown/dismissed, the message.complete hook,
the menu-open request) existed only for the card, so it goes with it; the
store keeps the eligibility read both remaining surfaces use.
2026-10-09 14:01:55 -07:00
Teknium
51e712b651 fix(skills): review fixes — raw fetch falls back on DNS block, restore never writes through links or over user files
- _fetch_file_bytes: a DNS-poisoned / NXDOMAIN raw.githubusercontent.com raises
  SSRFConnectionBlocked (a ValueError), which escaped the httpx.HTTPError suppress and
  failed the install before the Contents API fallback. Any transport failure on the raw
  attempt now falls back, and latches raw unusable on the source so a dropping firewall
  costs one 15 s timeout, not one per file.
- ensure_bundled_skill: a leftover dest is refused if any entry in it is a symlink or
  junction (copytree with dirs_exist_ok followed them out of skills/), and only missing
  files are copied (a user-edited file was overwritten). The canonical-path "present"
  check also requires the SKILL.md frontmatter name to match; a dir holding another
  skill's SKILL.md is refused, as on main.
- Docstring: a restore over leftover user files reads as user-modified, so update keeps it.
2026-10-09 13:58:16 -07:00
Teknium
18ee4251d8 fix(skills): hub installs download skill files from raw.githubusercontent.com, not one API call per file
`hermes-index rate_limited` (26 events / 9 installs in 14 days): GitHubSource.fetch
paid one Contents API call per file in the skill folder on top of repo + tree,
so an anonymous user (60/h) could never install a skill with ~58+ files and
four average skills drained the hour. Live on origin/main, anonymous:
skills-sh/remotion-dev/skills/remotion-best-practices cost 143 API calls and
failed rate_limited.

A file fetch pinned to the tree's commit sha now reads
raw.githubusercontent.com/<repo>/<sha>/<path> first: the same immutable bytes,
outside the REST limit. A raw miss (private repo, raw outage) falls back to the
Contents API as before. The same install now costs 2 API calls and succeeds
anonymously; anthropics/skills/pdf drops from 16 to 2.
2026-10-09 13:58:16 -07:00
Teknium
a1a0f0e030 fix(skills): restoring a built-in skill no longer fails on leftovers, and a failed restore is recorded as bundled
Post-fix telemetry still showed `hermes skills install llm-wiki` failing as
failure_class=filesystem_error, source=hub. Two restore paths failed for good:

- A skill dir left behind without its SKILL.md (a manual delete that kept user
  files, or a copy that died partway) made copytree raise EEXIST on every retry.
  The restore now copies around the leftovers (dirs_exist_ok); a dir with no
  SKILL.md is no skill, and user files in it are kept.
- A skill present behind a symlinked category (which the loader follows, so it
  works) was missed by the rglob scan, so the restore ran into the containment
  guard and failed. The canonical SKILL.md now counts as present.

A restore that still fails (read-only dir, a symlinked category with the skill
missing) was recorded as source=hub, because the failed exit returned no bundle
and _record_skill_install fell back to "hub". The bundled branch now marks the
attempt origin=bundled, so every bundled-path outcome, raised or returned,
records source=bundled.
2026-10-09 13:58:16 -07:00
ethernet
b624a38f21 Merge pull request #135801 from NousResearch/fix/is-junction-missing-path
fix(pm): is_junction returns False for a path that does not exist
2026-10-09 16:31:10 -04:00
ethernet
57e985d459 Update tests/pm/test_filesystem_is_junction.py
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-10-09 16:18:12 -04:00
ethernet
27808494c5 add nt check back in
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-10-09 16:17:41 -04:00
ethernet
bb706e92c7 fix(pm): is_junction returns False for a path that does not exist
d2639c0dc8 started probing plugins/<name> with is_junction before the
first install of a plugin. On Windows lstat() of that missing slot raised
FileNotFoundError, so every fresh plugin install crashed (caught by the
MSIX smoke's plugin step on rc.7-v0.21.7). os.path.isjunction answers
False for a missing path; match it, and let POSIX's absent reparse tag
fall into the same False.
2026-10-09 16:02:15 -04:00
Mariano Nicolini
3637c512fc docs(image_gen): state the fallback rule as no server accepted the request 2026-10-09 15:36:14 -04:00
Mariano Nicolini
ad6759294d fix(image_gen): keep the upscale on the FAL fallback and name the upscaler in the note 2026-10-09 15:36:14 -04:00
Mariano Nicolini
879573d722 docs(tool-gateway): attribute the 429 retries to the gateway 2026-10-09 15:36:14 -04:00
Mariano Nicolini
34907c36df fix(image_gen): describe the prompt as subject and scene when references carry the style 2026-10-09 15:36:14 -04:00
Mariano Nicolini
8daaaca084 fix(image_gen): say the FAL fallback also failed instead of claiming an image was generated 2026-10-09 15:36:14 -04:00
Mariano Nicolini
8d0c59d75c fix(image_gen): catch only the JSON error the fallback parse can raise 2026-10-09 15:36:14 -04:00
Mariano Nicolini
4710e757ab fix(image_gen): fall back only when Krea provably created no job, and apply the Krea default on first-run setup too 2026-10-09 15:36:14 -04:00
Mariano Nicolini
a9ebde190a feat(image_gen): fall back to FAL when managed Krea refuses a request before creating a job 2026-10-09 15:36:14 -04:00
Mariano Nicolini
257849838f feat(image_gen): default new paid subscribers to Krea 2 Medium Turbo so Krea is the image lane without a config edit 2026-10-09 15:36:14 -04:00
Mariano Nicolini
bcf216d461 feat(image_gen): describe Krea image inputs as style references so the model stops rewriting the style into the prompt 2026-10-09 15:36:14 -04:00
ethernet
b56a10246e Merge pull request #135757 from NousResearch/termux-fix
fix: termux script error
2026-10-09 14:47:30 -04:00
Maciej Lisiewski
13b699106d fix: termux script error 2026-10-09 20:30:20 +02:00
ethernet
26610b09a5 Merge pull request #135726 from unsupportedpastels/fix/pm-docker-baked-baseline
fix(pm): record the Docker image's baked extras and restore them on boot
2026-10-09 13:46:18 -04:00
unsupportedpastels
fe2b130b8b fix(pm): record the Docker image's baked extras and restore them on boot
The image bakes eight extras into /opt/hermes/.venv but never recorded
them as PM's dependency selection. The first generation a container built
(a lazy extra such as fal, a plugin's dependencies) started from an empty
selection, so it carried only what was requested and dropped aiohttp, the
messaging SDKs and mcp. After the next restart the API server, platform
adapters and MCP servers failed to start.

- pm/build_env.py, Dockerfile: --record-selection writes the frozen
  build's --extra list to the store facts as the venv selection, as a
  bundle build does. enabled-features.json is not written, so the frozen
  lazy-install policy stays out of Docker.
- pm/recovery.py: stage2's refresh restores extras the image ships that a
  volume's recorded selection lacks, through the normal explicit sync. On
  failure the image venv boots and the missing extras stay recorded for
  the next attempt. A malformed recorded fact is refused, as
  venv_is_current does.
- pm/install.py: share the PEP 685 extra-name key between
  _still_declared and the boot reconciliation.

Fixes #135329
2026-10-09 17:35:25 +00:00
ethernet
32b77f51c1 Merge pull request #135739 from NousResearch/ci/docker-publish-setup-python
ci(docker): pin Python 3.14 in the publish job
2026-10-09 13:34:27 -04:00
ethernet
8b497e2216 ci(docker): pin Python 3.14 in the publish job too
publish runs the same install-stamp and boundary scripts on the same
runner matrix, including arm64 whose system python3 is 3.10 and lacks
datetime.UTC. Without this, PR checks pass and main-push publish breaks.
2026-10-09 13:33:20 -04:00
ethernet
c9a99993b6 Merge pull request #135731 from NousResearch/ci/docker-setup-python
ci(docker): pin Python 3.14 in the build and test image job
2026-10-09 13:32:14 -04:00
ethernet
369be700e7 ci(docker): pin Python 3.14 in the build and test image job
The install stamp and profile boundary scripts ran on whatever python3
the runner image shipped. Set it up explicitly with actions/setup-python.
2026-10-09 13:29:27 -04:00
ethernet
cf23a1a5cc Merge pull request #135715 from NousResearch/ethie/lefthook-hookspath-nonfatal
fix: don't fail npm install when lefthook can't install hooks
2026-10-09 13:09:07 -04:00
ethernet
4b30ad4282 Merge pull request #135464 from NousResearch/fix/rc5-termux
Unconfigured hermes --tui opens the TUI setup screen again (unblocks the Termux .deb, salvage #130913)
2026-10-09 13:09:01 -04:00
ethernet
040dbb9053 Merge pull request #135348 from NousResearch/harder-linter-fixes-part2
Harder linter fixes part 2
2026-10-09 13:08:10 -04:00
ethernet
894a26a39e Merge pull request #135459 from NousResearch/fix/rc5-darwin-baseline
fix(release): stable releases pass signed-package acceptance when no upgrade baseline exists
2026-10-09 13:06:34 -04:00
ethernet
62527de9d4 Merge pull request #135458 from NousResearch/fix/pm-uv-cache-reseed
fix(pm): updated installs get the new bundle's uv cache, so plugin enable stops building cryptography from source
closes #131961
closes #135140
2026-10-09 13:05:10 -04:00
ethernet
dc140b70d5 Merge pull request #135460 from NousResearch/fix/rc5-e2e
fix(e2e): macOS ffmpeg mirror fallback and app quit wait
2026-10-09 13:04:07 -04:00
ethernet
0668184e59 fix: don't fail npm install when lefthook can't install hooks
lefthook refuses to install when core.hooksPath is set globally and
exits non-zero. prepare forwarded that status, so npm ci failed and
took down hermes update's node-deps step (desktop, tui and web).
Hooks are a contributor convenience; lefthook already prints its own
remediation, so let it warn and keep the install going.
2026-10-09 12:59:45 -04:00
GrgaPalcic
2af85c331a fix(desktop): make mermaid SVG well-formed via the HTML parser, not a void-tag regex
The void-tag pass from the previous commit fixes `<br>`, but mermaid's
label HTML carries more than void tags: a non-breaking space (`&nbsp;`,
mermaid's `#nbsp;`, or a literal U+00A0 in the source) is serialised as
the `&nbsp;` entity, which XML does not define. Such a label still
decodes to a broken image.

mermaid.render() returns the HTML serialisation of the diagram, so parse
it with the HTML parser it was written for and re-serialise it with
XMLSerializer. That fixes the whole class at once. Against mermaid
11.16 in Chromium, across flowchart, class, state, ER, mindmap, block,
kanban, sequence, journey, gantt, pie, timeline, gitGraph, quadrant and
xychart output, the round-trip yields the same element tree and image
size as the regex wherever the regex output is valid XML, and also loads
the non-breaking-space cases the regex leaves broken.

Drop a label's plain `xmlns` attribute before serialising: the
serializer declares the namespace itself, and a spec-conformant one
(jsdom) writes both, a duplicate attribute.

The tests' fixtures put `<br>` directly in SVG <text>, which mermaid
never emits; replace them with the real 11.16 label shape (XHTML in a
<foreignObject>) and assert the label keeps its line break and its
non-breaking space.

Refs #133089, #134031
2026-10-09 11:57:18 -05:00
flyer
766ab6dd2d fix(desktop): render Mermaid labels that contain <br/>
`mermaid.render()` returns SVG aimed at inline HTML, and its sanitizer
gives back a `<br/>` written in a label as an open `<br>`. That string is
not well-formed XML, so the strict `image/svg+xml` parse in
`normalizeSvgSize` yields the parsererror document instead of an `<svg>`,
takes the `el.tagName !== 'svg'` early return, and hands the *unnormalized*
string back. The diagram `<img>` then gets it as a `data:` URI, which Blink
decodes as strict XML and rejects for the same reason — so the message shows
a broken image whose alt text reads "Mermaid diagram", with nothing thrown
and nothing written to any log.

Close HTML void tags before the markup reaches either XML parse. The pass is
generic rather than a `<br>` special case, because the same class of input
reaches `<hr>`, `<img>`, `<input>`, …, and it is idempotent: already-closed
tags and non-void tags (including names that merely start with a void tag,
such as `<break>`) pass through untouched.

Fixes #133089

Verified by replaying 32 real ```mermaid fences from a session database
through the app's own renderer chunks in Electron 40.10.2 / Chromium 144:
24/32 fail before this change — every failure has `<br/>` in a label, with a
perfect 1:1 correlation — and 31/31 renderable diagrams succeed after it. The
32nd is an invalid Mermaid source that correctly keeps falling back to its
source block.
2026-10-09 11:57:18 -05:00
Riccardo Vecchi
44c32dfa79 fix(pm): publish seeded cache files atomically
Follow up the shared-cache copy observation on PR #135458 with behavioral regression coverage and a sibling staging file. Make the lock-change fixture independent of timestamp granularity.

Prepared with OpenAI Codex assistance; no human review claimed.
2026-10-09 12:56:40 -04:00
Maciej Lisiewski
2ff58849f4 ci: ruff UP037 fixes 2026-10-09 17:34:38 +02:00
Maciej Lisiewski
9eab688b42 ci: ruff UP017 fixes 2026-10-09 17:34:36 +02:00
Maciej Lisiewski
aff64c3eeb ci: UP007, UP022, UP028, UP031, UP041 linter fixes 2026-10-09 17:31:52 +02:00
Maciej Lisiewski
32b70e9733 ci: FURB188, FURB192 linter fixes 2026-10-09 17:31:52 +02:00
Jeffrey Quesnelle
e0550c97bb Merge pull request #135438 from NousResearch/fix/local-recs-weak-hardware
fix(local-models): stop recommending the 27B on hardware that can't run it well
2026-10-09 11:19:21 -04:00
Ben Barclay
0670ba4524 fix(tui_gateway): a settled client-gone interrupt beats the wakeup-schedule exemption
An interrupted detached turn sets the client-gone and turn-cancel latches. If
the active /loop or /heartbeat then re-armed the orphan timer after the turn
settled, neither latch ever cleared: no tick could claim the turn and every
reattach was refused with 4009, so the loop never reached its budget and the
session stayed pinned. Collect the settled session as before instead.

Also: TTL eviction checks the cheap clock before the store-reading exemptions,
and the heartbeat branch gets its own reaper tests (active local pins; paused,
cleared or gateway-owned releases).
2026-10-09 09:35:31 -04:00