mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 19:27:03 +01:00
helium: unlock the keyring through secretstorage; anchor the running-browser check
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
`secret-tool lookup` is non-interactive — on a locked keyring it returns nothing and shows no dialog — and a busctl Unlock+Prompt pair cannot work either, because Secret Service prompt objects die with the D-Bus connection that created them. secretstorage keeps one connection: get_default_collection().unlock() pops the gcr dialog and blocks until it is answered. Verified on the real profile: 5 v11 rows -> v10, then with the keyring re-locked Helium (through the wrapper, so the flags file and LeechBlock apply) lists all 5 logins and keeps every row. `pgrep -f /opt/helium-browser-bin` also matched the invoking shell when its argv mentioned the path, so the guard refused on an idle machine; now anchored to `^/opt/helium-browser-bin/`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M8YGXVZdNUG79q5DGsdMm3
This commit is contained in:
parent
b627140cb2
commit
ec2da0b575
@ -279,6 +279,7 @@ repos:
|
||||
# Chromium os_crypt blobs (AES-CBC).
|
||||
- websockets
|
||||
- cryptography
|
||||
- secretstorage
|
||||
# Test suites and conftest fixtures are linted separately; they use
|
||||
# patterns (protected-access, missing docstrings, fixture shadowing)
|
||||
# that don't belong in the source-code 10/10 gate.
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
{
|
||||
"intent": "Give the artifact cap a one-command fix, so the gate can never block a commit with no way out except reconstructing today's selection logic by hand.",
|
||||
"scope": [
|
||||
"meta/scripts/check_repo_size.sh - adds --prune",
|
||||
"Non-goal: pruning anything now. The tree is at 21/21, well under the 60 cap."
|
||||
],
|
||||
"changes": [
|
||||
"Added --prune: stages the removal of the oldest artifacts, keeping the newest KEEP_ARTIFACTS=20 per directory plus template.json, and leaves the commit to the operator.",
|
||||
"Fixed a same-day ordering bug found by running it: the first version sorted on %cs (date only), so every artifact committed on the same day tied, sort fell back to comparing the path, and the prune deleted alphabetically. It removed cap-artifact-growth-2026-08-23.json - one of the newest files - instead of the oldest. Now sorts on %ct (unix seconds) with sort -rn."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "./meta/scripts/check_repo_size.sh --prune (in a detached worktree)",
|
||||
"result": "pass",
|
||||
"evidence": "First run staged 2 deletions but they were WRONG: cap-artifact-growth-2026-08-23.json and flatten-linux-configuration-2026-08-23.json, both same-day files chosen alphabetically. This is what exposed the %cs tie."
|
||||
},
|
||||
{
|
||||
"command": "./meta/scripts/check_repo_size.sh --prune after the %ct fix (fresh worktree)",
|
||||
"result": "pass",
|
||||
"evidence": "Now stages quarantine-arch-sysctl-cases-2026-08-22.json and thorium-repairs-tests-2026-08-23.json - the genuinely oldest. Templates survive; contracts correctly reports 'nothing to prune' at 20 + template."
|
||||
},
|
||||
{
|
||||
"command": "./meta/scripts/check_repo_size.sh; pre-commit run --files meta/scripts/check_repo_size.sh",
|
||||
"result": "pass",
|
||||
"evidence": "'1050 source files (ceiling 1200)' EXIT=0; shfmt and shellcheck clean. Tested in a throwaway worktree so the real tree was never modified."
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"--prune stages deletions rather than committing them, so an operator who runs it and forgets to review still has git diff --cached as the checkpoint.",
|
||||
"KEEP_ARTIFACTS=20 is below MAX_ARTIFACTS_PER_DIR=60 on purpose; a prune therefore buys headroom instead of landing back on the limit."
|
||||
],
|
||||
"rollback": [
|
||||
"git revert the commit. --prune is additive and off by default; the enforcing path is unchanged.",
|
||||
"After rollback, confirm ./meta/scripts/check_repo_size.sh still exits 0 and prints the count."
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
{
|
||||
"intent": "Make migrate_helium_passwords_to_basic.py actually work on the user's box: the first version handed back twice never showed the keyring dialog, and the running-Helium guard false-positived on an idle machine.",
|
||||
"scope": [
|
||||
"linux_configuration/installers/migrate_helium_passwords_to_basic.py (unlock via secretstorage; anchored process check)",
|
||||
"linux_configuration/installers/verify_helium.py (anchored process check)",
|
||||
"linux_configuration/installers/install_helium.sh (+python-secretstorage dep)",
|
||||
".pre-commit-config.yaml pylint deps, meta/requirements.txt (+secretstorage 3.5.0)"
|
||||
],
|
||||
"changes": [
|
||||
"`secret-tool lookup` is non-interactive: on a locked keyring it returns nothing and shows no dialog. A busctl Unlock+Prompt sequence also cannot work because Secret Service prompt objects die with the connection that created them. secretstorage keeps one D-Bus connection: get_default_collection().unlock() pops the gcr dialog and blocks until answered.",
|
||||
"`pgrep -f /opt/helium-browser-bin` matched the invoking shell whose argv mentioned the path, so the guard reported Helium running on an idle machine; the pattern is now anchored to `^/opt/helium-browser-bin/`."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "linux_configuration/installers/migrate_helium_passwords_to_basic.py (run by Claude, user answered the gcr dialog)",
|
||||
"result": "pass",
|
||||
"evidence": "keyring is locked — answer the unlock dialog on screen; backup: Default/Login Data.pre-basic-20260920-182556; converted 5 v11 -> v10 rows; 0 already v10; added --password-store=basic to ~/.config/helium-browser-flags.conf"
|
||||
},
|
||||
{
|
||||
"command": "keyring re-locked via Secret.Service.Lock; /usr/local/bin/helium-browser (wrapper) on the real profile under Xvfb; chrome://password-manager/passwords deep-text; chrome://version command line; /json/list",
|
||||
"result": "pass",
|
||||
"evidence": "all 5 sites listed (kuhy-cloud.duckdns.org, google.com, typeracer.com, leetcode.com, infakt.pl); command line has --password-store=basic and the LeechBlock --load-extension; uBO background_page present; keyring still locked afterwards; Login Data still 5 rows, all v10 (nothing deleted on read)"
|
||||
},
|
||||
{
|
||||
"command": "pre-commit run --files <changed files> (in the commit worktree)",
|
||||
"result": "pass",
|
||||
"evidence": "ruff, ruff-format, pylint, shellcheck, codespell, file-length, dependency-freshness green"
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"secretstorage's unlock() blocks with no timeout until the dialog is answered or dismissed; the script says so on stdout before it blocks.",
|
||||
"The backup file Login Data.pre-basic-<ts> still holds the v11 (keyring-encrypted) blobs; it is useless without the keyring key and can be deleted once the user is happy."
|
||||
],
|
||||
"rollback": [
|
||||
"Restore Default/'Login Data.pre-basic-20260920-182556' over 'Login Data' and remove --password-store=basic from ~/.config/helium-browser-flags.conf.",
|
||||
"Validate: Helium prompts for the keyring again and the 5 logins decrypt after unlock."
|
||||
]
|
||||
}
|
||||
@ -53,7 +53,7 @@ usage() {
|
||||
|
||||
# Everything the clone + verify steps fork: rsync, Xvfb, the CDP websocket
|
||||
# client; base-devel + git only matter on a box without yay (makepkg path).
|
||||
readonly -a PACMAN_DEPS=(rsync xorg-server-xvfb python-websockets python-cryptography base-devel git)
|
||||
readonly -a PACMAN_DEPS=(rsync xorg-server-xvfb python-websockets python-cryptography python-secretstorage base-devel git)
|
||||
|
||||
validate_requirements() {
|
||||
command -v pacman >/dev/null 2>&1 || {
|
||||
|
||||
@ -11,8 +11,9 @@ Linux os_crypt scheme (unchanged since Chromium 4x): key = PBKDF2-HMAC-SHA1
|
||||
(secret, "saltysalt", 1 iter, 16 B); AES-128-CBC, IV = 16 spaces, PKCS#7;
|
||||
``v11`` uses the keyring secret, ``v10`` uses the fixed string "peanuts".
|
||||
|
||||
Needs the keyring unlocked ONCE — secret-tool pops the unlock dialog, so run
|
||||
this yourself, with the browser closed. Never prints a secret.
|
||||
Needs the keyring unlocked ONCE: it asks the Secret Service to unlock the
|
||||
default collection, which pops the gcr dialog. Browser must be closed.
|
||||
Never prints a secret.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@ -28,6 +29,7 @@ import time
|
||||
|
||||
from cryptography.hazmat.primitives import padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
import secretstorage
|
||||
|
||||
DEFAULT_PROFILE = Path.home() / ".config" / "net.imput.helium" / "Default"
|
||||
KEYRING_SCHEMA = "chrome_libsecret_os_crypt_password_v2"
|
||||
@ -37,7 +39,8 @@ KEY_LEN = 16
|
||||
BASIC_SECRET = b"peanuts"
|
||||
V10 = b"v10"
|
||||
V11 = b"v11"
|
||||
HELIUM_PROCESS_MARK = "/opt/helium-browser-bin"
|
||||
# Anchored: a shell whose argv merely mentions the path must not match.
|
||||
HELIUM_PROCESS_MARK = "^/opt/helium-browser-bin/"
|
||||
FLAGS_FILE = Path.home() / ".config" / "helium-browser-flags.conf"
|
||||
BASIC_FLAG = "--password-store=basic"
|
||||
|
||||
@ -69,13 +72,27 @@ def encrypt(key: bytes, plain: bytes) -> bytes:
|
||||
|
||||
|
||||
def keyring_secret(application: str) -> bytes:
|
||||
"""Fetch the browser's Safe Storage secret (this pops the unlock dialog)."""
|
||||
for attrs in (["application", application], []):
|
||||
cmd = ["/usr/bin/secret-tool", "lookup", "xdg:schema", KEYRING_SCHEMA, *attrs]
|
||||
out = subprocess.run(cmd, capture_output=True, check=False).stdout
|
||||
if out:
|
||||
return out.rstrip(b"\n")
|
||||
msg = f"no {KEYRING_SCHEMA} item in the keyring (locked? dialog cancelled?)"
|
||||
"""Unlock the default keyring (this pops the gcr dialog) and read the key.
|
||||
|
||||
One D-Bus connection for the whole exchange: Secret Service prompt objects
|
||||
live only as long as the connection that created them, which is why a
|
||||
`secret-tool`/`busctl` sequence of separate processes can never work.
|
||||
"""
|
||||
conn = secretstorage.dbus_init()
|
||||
collection = secretstorage.get_default_collection(conn)
|
||||
if collection.is_locked():
|
||||
say("keyring is locked — answer the unlock dialog on screen")
|
||||
if collection.unlock(): # True means the dialog was dismissed
|
||||
msg = "unlock dialog dismissed; keyring still locked"
|
||||
raise SystemExit(msg)
|
||||
for attrs in (
|
||||
{"xdg:schema": KEYRING_SCHEMA, "application": application},
|
||||
{"xdg:schema": KEYRING_SCHEMA},
|
||||
):
|
||||
items = list(collection.search_items(attrs))
|
||||
if items:
|
||||
return items[0].get_secret()
|
||||
msg = f"no {KEYRING_SCHEMA} item in the default keyring"
|
||||
raise SystemExit(msg)
|
||||
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ from typing import Self
|
||||
from websockets.sync.client import connect
|
||||
|
||||
HELIUM_BIN = "/usr/bin/helium-browser"
|
||||
HELIUM_BIN_DIR = "/opt/helium-browser-bin"
|
||||
HELIUM_BIN_DIR = "^/opt/helium-browser-bin/" # anchored pgrep -f pattern
|
||||
DEFAULT_PROFILE = str(Path.home() / ".config" / "net.imput.helium")
|
||||
UBO_ID = "blockjmkbacgjkknlgpkjjiijinjdanf"
|
||||
POLICY_DIR = Path("/etc/chromium/policies/managed")
|
||||
|
||||
@ -81,6 +81,7 @@ ruff==0.16.8
|
||||
# safety is deliberately not pinned: its only reference in this repo is a
|
||||
# commented-out pre-commit hook, and safety 3.8.1 declares `typer<0.26`,
|
||||
# which would hold the typer this repo actually imports below latest.
|
||||
secretstorage==3.5.0
|
||||
selenium==4.49.0
|
||||
typer==0.27.2
|
||||
types-python-dateutil==2.9.0.20260807
|
||||
|
||||
Loading…
Reference in New Issue
Block a user