Commit Graph
20 Commits
Author SHA1 Message Date
Lucas Coutinho 038bf9924f fix: _stopMic skips recognition.stop() when raw audio mode is active
Bug: when raw audio mode is enabled, SpeechRecognition is
skipped and MediaRecorder is used instead. But _stopMic()
checked for a truthy 'recognition' reference (which exists
even when unused) and called recognition.stop() — a no-op
since speech recognition was never started — never reaching
the mediaRecorder.stop() branch. Recording could never be
stopped by the mic button.

Fix: add !_rawAudioMode guard to the recognition.stop() branch.
2026-05-31 04:34:17 +00:00
Lucas Coutinho 5ebf98479d fix: rename _updateMicBadge → _updateMicTooltip, use monkeypatch in tests
- 'Badge' naming was misleading since the visual badge was removed
- Use monkeypatch.setitem with pytest fixture instead of manual
  import sys + del sys.modules (safer cleanup, no inline import)
- All 9 tests pass
2026-05-31 04:33:52 +00:00
Lucas Coutinho 6258afbaef fix: tooltip conflict with RAW badge — use span instead of ::after
The .has-tooltip CSS uses ::after to show the tooltip text.
Using ::after for the RAW badge overwrote the tooltip content.
Fixed by adding a <span class="mic-raw-badge"> child element via JS.

Also: use _setButtonTooltipAndKey for consistent i18n tooltip management.
2026-05-31 04:33:52 +00:00
Lucas Coutinho a1e703203d chore: remove stale plan files from previous branch 2026-05-31 04:33:52 +00:00
Lucas Coutinho f6b658c7a2 fix: use _setButtonTooltipAndKey for mic tooltip to update data-i18n-title
The global _setButtonTooltip only sets data-tooltip, not data-i18n-title.
The i18n system reads data-i18n-title for locale-aware tooltip updates.
Switched to _setButtonTooltipAndKey which manages both attributes.
2026-05-31 04:33:52 +00:00
Lucas Coutinho 41a8b1deb0 feat: add raw audio upload mode — send audio as attachment instead of transcribing
- Settings toggle in Sound section (after voice mode button)
- Mic button shows 'RAW' badge when raw audio mode is active
- Raw mode: record → pending file → send() (auto-send if textarea empty)
- Dictation mode: unchanged (transcribe → textarea)
- 12 locales with full i18n keys and translations
- 4 backend tests for upload + transcribe regression

PR: Thinking Path
- Hermes WebUI has dictation via Web Speech API / MediaRecorder → /api/transcribe
- Telegram sends raw audio as attachment, agent decides what to do
- Raw audio mode enables external STT, emotion/noise analysis, multimodal models

What Changed
- static/index.html: settingsRawAudio checkbox in Sound section
- static/boot.js: raw audio preference, send raw blob as pending file
- static/style.css: badge RAW on mic button
- static/i18n.js: 12 locales with translations
- tests/test_raw_audio_upload.py: 4 tests for upload + regressions

AI Usage
- Provider: opencode-go
- Model: deepseek-v4-flash
2026-05-31 04:33:52 +00:00
Lucas Coutinho fe4689e280 test(auth): merge invalidation tests into hash cache test file, remove duplicate 2026-05-13 16:17:44 -03:00
Lucas Coutinho 2a96fb4a5b fix(auth): update HMAC sig length assertion to 64 chars and rebase on PR1 2026-05-13 14:19:09 -03:00
Lucas Coutinho b734d95bc0 test(auth): add regression tests for HMAC migration bridge (32→64 char) 2026-05-13 14:18:47 -03:00
Lucas Coutinho 7e6f7372d5 fix(auth): add type hint to verify_session() 2026-05-13 14:18:47 -03:00
Lucas Coutinho 9921bbb412 docs(auth): add X-Forwarded-Proto trust warning to _is_secure_context() 2026-05-13 14:18:47 -03:00
Lucas CoutinhoandClaude Sonnet 4.6 07a5fe0838 fix(auth): HMAC length migration bridge and restore Secure cookie heuristic
HMAC length: create_session() now emits a full 64-char HMAC-SHA256 hex
digest instead of the truncated 32-char form. verify_session() accepts
both lengths during a transition window so existing sessions survive the
upgrade without a forced global logout. The legacy 32-char branch can be
removed once the default 30-day session TTL has elapsed.

Secure flag: introduce _is_secure_context(handler) to encapsulate the
env-var override and heuristic. Restores the getpeercert / X-Forwarded-Proto
heuristic that was present before this refactor, keeping the env-var
override (HERMES_WEBUI_SECURE) on top for proxy deployments that need
explicit control. The bare `return False` stub that the previous commit
left in place silently broke Secure-cookie delivery for all reverse-proxy
users who never set the env var.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:18:47 -03:00
Lucas Coutinho 3daa12ceb0 test(auth): add cache invalidation regression tests for save_settings() 2026-05-13 14:09:39 -03:00
Lucas Coutinho 2bcf411519 fix(auth): invalidate password hash cache in save_settings() on password change 2026-05-13 14:08:37 -03:00
Lucas Coutinho 978dbc15d8 fix(auth): correct misleading cache invalidation comment in verify_password() 2026-05-13 12:48:35 -03:00
Lucas Coutinho 8ca29618fe fix(auth): tighten except to OSError, add type hints, fix test imports 2026-05-13 12:27:27 -03:00
Lucas Coutinho 720e69cb83 fix(auth): cache signing and PBKDF2 keys in memory, remove migration side-effect call 2026-05-13 11:13:23 -03:00
Lucas CoutinhoandClaude Sonnet 4.6 e6e91e4973 fix(auth): thread-safe login rate limiter, PBKDF2 key separation, and migration path
Concurrent failed logins raced on _login_attempts because no lock guarded
the dict. Add _LOGIN_ATTEMPTS_LOCK and wrap both _check_login_rate() and
_record_login_attempt() with it.

Extract _load_key() to de-duplicate key file I/O. Add _pbkdf2_key() that
loads .pbkdf2_key (separate from .signing_key) so PBKDF2 and HMAC signing
no longer share a key — key reuse across cryptographic primitives is unsafe.

Update _hash_password() to use _pbkdf2_key() as its default salt, with an
optional *salt* kwarg so verify_password() can try the legacy .signing_key
salt during transparent migration. When the old hash matches, save_settings()
re-hashes with _pbkdf2_key() and _invalidate_password_hash_cache() ensures
the next request sees the upgraded hash without a restart.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 10:40:23 -03:00
Lucas Coutinho 7acbb3d99d Cache PBKDF2 password hash to eliminate ~1s overhead on every HTTP request
get_password_hash() computes PBKDF2-SHA256 with 600k iterations to
hash the HERMES_WEBUI_PASSWORD env var.  This is called on nearly every
HTTP request via check_auth -> is_auth_enabled -> get_password_hash.

Before: ~1s of PBKDF2 per request, regardless of how many times the
same env-var value has already been hashed.  A page load hitting 5+
API endpoints would burn 5+ seconds purely on password hashing.

After: compute once on first call, cache the hex result in a module-
level variable.  Subsequent calls are a single global-variable read
(~50ns).  The env var is immutable for the process lifetime, so there
is nothing to invalidate.

Thread-safe: double-checked locking ensures that under a burst of
concurrent requests only one thread computes PBKDF2, while the fast
path (after initialisation) requires zero locks.

10 unit tests covering all branches, cache-lifetime semantics, and
concurrent burst safety (8 threads, exactly 1 PBKDF2 call).
Test isolation: reloads only api.auth via importlib.reload, leaving
api.config untouched so test_pytest_state_isolation.py is unaffected.

Security analysis: zero regression.  The hash is derived from a static
env var and a static signing key — both already readable from process
memory.  Caching does not introduce any new disclosure or replay
vector.  PBKDF2 is still used for the initial computation and for
verify_password() on login.

AI: deepseek/deepseek-v4-flash
2026-05-13 00:54:50 -03:00
Lucas Coutinho bc3f4e54a6 Cache PBKDF2 password hash to eliminate ~1s overhead on every HTTP request
get_password_hash() computes PBKDF2-SHA256 with 600k iterations to
hash the HERMES_WEBUI_PASSWORD env var.  This is called on nearly every
HTTP request via check_auth -> is_auth_enabled -> get_password_hash.

Before: ~1s of PBKDF2 per request, regardless of how many times the
same env-var value has already been hashed.  A page load hitting 5+
API endpoints would burn 5+ seconds purely on password hashing.

After: compute once on first call, cache the hex result in a module-
level variable.  Subsequent calls are a single global-variable read
(~50ns).  The env var is immutable for the process lifetime, so there
is nothing to invalidate.

Thread-safe: double-checked locking ensures that under a burst of
concurrent requests only one thread computes PBKDF2, while the fast
path (after initialisation) requires zero locks.

Security analysis: zero regression.  The hash is derived from a static
env var and a static signing key — both already readable from process
memory.  Caching does not introduce any new disclosure or replay
vector.  PBKDF2 is still used for the initial computation and for
verify_password() on login.

AI: deepseek/deepseek-v4-flash
2026-05-13 00:25:41 -03:00