Files
app/extension

Current dedicated human-vault taste test

Version 0.3.6 sends human-account requests only to https://teahub.xyz/tea/site/chat/persimmons/human-vault/items. Sign in to shared Teahub, then connect from the product surface at https://persimmons.app. List/item reads use empty POST /read routes so Chromium sends its genuine extension Origin; the pinned server allowlist is unchanged. Connect Persimmons requests only the exact Persimmons host permission. The pinned extension ID remains kiolgjoohhgdohcafgelhikpgkbmckbg.

Human vault keys are namespaced by Persimmons origin and account. Existing Tea ciphertext/local keys are not migrated or overwritten. The separate Actions tokens / Juice controls retain their existing explicit Tea permission and session flow. They do not use the new human vault.

Download the extension ZIP from the Persimmons homepage, extract it, and load persimmons-extension/ through Chromium Developer mode. Full steps and deployment receipt: dedicated host.

The historical v0 description below applies to the separate Actions/Juice controls.

Persimmons Chromium extension v0

Standalone MV3; load this persimmons-extension/ directory using Developer mode → Load unpacked at chrome://extensions. No build or dependency install is needed.

Use

  1. Sign in to https://teahub.xyz in the same browser profile. Visit an HTTPS site such as https://wp.phly.cloud/wp-admin/ and click the Persimmons toolbar icon.
  2. Review Current origin and Authentication unknown. The capture signal is only the current origin. No cookies, page content, passwords or passkeys are read.
  3. Connect Teahub / refresh requests only https://teahub.xyz/* permission. Your browser sends its existing Teahub session; no service identity is substituted.
  4. To Save authority to My vault, explicitly paste a provider API token / PAT into the masked field and enter a public source name. The existing create API requires a credential value; origin metadata alone cannot create authority. The field clears on success, failure and popup close. Refresh after ambiguous saves.
  5. Under Agent access / Permissions, review requested scopes (read + draft-write by default), expiry (60 minutes by default), choose a source and enter a public agent/workflow subject. Squeeze scoped Juice persists policy metadata.
  6. Revoke changes that Juice policy's status without deleting its source token. Open Persimmons opens the existing hosted vault UI.

Juice v0 is policy metadata only. It does not enforce downstream scopes or expiry, perform provider calls, attenuate tokens, or revoke provider sessions. Public names, subjects and notes must contain no secrets. Provider privileges and validity remain unchanged until revoked at the provider.

Existing API and storage

No backend, vault, CORS or MCP changes. Fixed base: https://teahub.xyz/tea/site/chat/persimmons.

Action Existing route
List sources GET /list (popup projects My vault only)
Save explicit token POST /create with vault_kind: "user"
List Juice GET /juice/list
Squeeze POST /juice/create with source, subject, scopes, expiry, My vault
Revoke Juice POST /juice/revoke with ID and My vault
Agent read MCP persimmons_juice_list(repository?, source_id?)

Requests include browser credentials, reject redirects, bypass cache, have a 15-second timeout, and cap encoded bodies at 24 KiB (tokens at 16 KiB). POST uses the existing browser client's JSON-as-text/plain contract. Errors are fixed messages; response bodies are never dumped. Only public metadata is rendered.

The existing Gitea Actions secret stores the token; Actions variables store connection and Juice metadata. Those variables are not secret storage. The extension adds no encryption: at-rest protection is delegated to the existing Gitea secret store and its operator configuration, not independently audited here. This is not end-to-end encryption; Teahub receives plaintext over HTTPS and Gitea/authorized Actions retain their existing access. There is no reveal endpoint or new vault. The legacy Actions/Juice module has no storage persistence or content script; the human-vault module and status worker are described below. Tokens exist briefly in JS/network memory; memory erasure is not guaranteed.

Threat limits

Only activeTab and optional Teahub host permission are requested. The current tab URL is reduced to an origin; paths, query strings and fragments are not saved. Authentication is deliberately unknown, not inferred from cookies or page text. The extension never scrapes passwords or exports passkey private keys.

A compromised browser, extension, Teahub host, or authorized Gitea/Actions operator can still misuse a submitted token. Host grants are hostname-based and include all ports. Browser SameSite/origin policy can block session transport; failure stays closed and no cookie is manually injected. Incognito is disabled. Closing the popup can interrupt operations; refresh before retrying. Revoking Juice does not revoke the token, sign out the browser or enforce downstream permissions.

Chrome documents activeTab access and extension host permissions.

Verification

From repository root:

node --test persimmons-extension/tests/*.test.mjs
XDG_DATA_HOME=/tmp/persimmons-test-data python -m unittest tests.test_persimmons tests.test_persimmons_web tests.test_persimmons_vault_model tests.test_persimmons_juice tests.test_persimmons_juice_http

Focused tests exercise permission denial, token save and clearing on failure, origin minimization, safe failures, scoped Juice/revoke and startup refresh. The registered MCP Juice tool is exercised with a generated synthetic sentinel: it never reads a secret route or returns a source credential, including an injected extra credential field in stored Juice metadata. Existing vault isolation/lifecycle checks remain in place. These are not downstream enforcement tests.

For a signed-in live taste test, use a disposable provider token: follow Use above, reopen to check persistence, call persimmons_juice_list, revoke Juice and verify its revoked status. Deny Teahub permission, log out and simulate offline responses; expect safe failures. Never put token values or cookie data in screenshots/logs.

Next: authenticated disposable-token acceptance; a caller-authorized executor that enforces Juice policy; provider-native OAuth/token rotation and revocation.

0.3.3 taste-test UX

The packaged orange icons cover Chromium 16/32/48/128 sizes. Keep the same loaded folder/profile and reload after extracting the updated ZIP; the pinned ID is unchanged. Connect → Unlock → Save an account for this origin reveals manual username/password entry only on the accepted HTTPS origins. Encrypt and save is the sole capture action; typing, navigating, and signing in never capture. Persimmons' own login stays outside the accepted human-vault origin allowlist. Actions/Juice remains available under a collapsed section with its existing separate Teahub permission and auth flow.

The homepage checks the actual account session, hides redundant sign-in when signed in, and labels its ZIP link Get / update. It does not infer installation or unlock state. Account forms request autocomplete off and retain real masked password inputs, validation and clearing; browsers can override that hint and still offer save/autofill. No browser-wide password-manager setting is changed.

References: browser autocomplete limits, Chromium icon packaging.

0.3.4 connection, controlled acceptance and updates

Connect previously lived only in the popup's owner variable; closing the popup lost it. Now chrome.storage.local retains only {owner} in a dedicated connection hint, in addition to the existing passphrase-wrapped key. Every reopen verifies permission and the live same-owner server session before showing Connected/Locked. A changed account requires explicit Connect; offline/expired sessions fail closed. Passphrases, plaintext credentials and unwrapped keys are never persisted.

The only new background capability is a read-only external status message from the top-level exact https://persimmons.app origin. It compares the page's owner, the saved connection hint and the live server owner, returning only a connected boolean and package version. There is no page command to unlock, fill, reload, read credentials or retrieve keys, and no unlocked signal. The homepage checks on load, focus and Refresh. Old extensions produce an unavailable/update message. The existing exact server extension ID and two-origin human-vault allowlist remain.

Actions tokens / Juice is now a small collapsed divider, with token authority, scope/expiry and passkey details under separate disclosures. Orange assets and hero/footer source are unchanged.

Stable-folder update loop

Use the homepage or popup Update extension link. Save https://persimmons.app/assets/update-extension.py once and run:

python3 update-extension.py "/path/to/already-loaded/persimmons-extension"

Close the popup first. The Python 3 helper downloads the dedicated host's HTTPS ZIP, refuses redirects/downgrades, validates bounded archive paths and the pinned manifest key/ID, then replaces files in that same folder (manifest last). It does not access browser storage, run downloaded commands, install a native host or open a local network listener. This uses TLS release trust; the public manifest key is an identity check, not an independent package signature. If interrupted, rerun before Reload. Existing unrelated folder files are retained.

Then use Reload at chrome://extensions, or, from 0.3.4 onward, popup Update extension → Reload installed files. Retain the same browser profile and do not uninstall or clear storage. The first update from 0.3.3 uses Chrome's Reload. Future rounds reuse the command and folder, without manual download/unzip/select. Python must already be installed (py on Windows). Manual in-place ZIP replacement remains a fallback. The helper was exercised on Linux; macOS/Windows are not yet browser-tested.

An unpacked extension cannot write its installation directory with its extension APIs; runtime.reload() rereads installed files and does not download them. requestUpdateCheck() is for packaged updates; setting an update URL does not turn a loaded unpacked directory into a Web Store installation. See Chrome's local development/reload instructions and update lifecycle.

Shortest automatic-update distribution path (not published)

With explicit approval, use a Chrome Web Store developer account, prepare listing and privacy/data-use disclosures plus reviewer instructions, and upload a versioned package. Private with named Google-account testers is the narrow taste-test choice; Unlisted allows anyone with the link. Both require review. Approved store-installed packages receive Chrome-managed updates, with later versioned ZIPs submitted through the same listing. See distribution settings and updating a listing.

Identity is a pre-publication gate: inspect the assigned store public key/ID and verify it equals kiolgjoohhgdohcafgelhikpgkbmckbg. Do not assume that including an arbitrary manifest public key forces the Store to use it. Establish whether the original signing identity can be retained through the supported upload process; no private signing key was sought or exported in this round. If the Store assigns a different ID, stop for an explicitly reviewed identity/key-storage migration; do not widen the server caller allowlist or tell testers to uninstall their existing vault. See Chrome's key/ID guidance.

Retest 0.3.4

  1. Update the same folder and Reload; sign in at persimmons.app.
  2. Open https://wp.phly.cloud/wp-login.php, click 🍊, Connect once and accept the exact Persimmons permission. Initialize/unlock with your vault passphrase.
  3. Expand Save an account, type a disposable username/password and Encrypt and save.
  4. Close the popup, refresh the login page, reopen 🍊: Connected/Locked should return automatically, with empty passphrase and Fill disabled. Unlock, select the saved account, Fill current page. Both fields should fill; nothing submits.
  5. Delete that account. Refresh persimmons.app: Extension connected, with no claim that the vault is unlocked. Sign out: reconnect fails safely; sign back into the same account to restore connection. A different account requires explicit Connect.

Automated acceptance now uses the real controlled WordPress login form, including actual popup destruction/reopen, both page refreshes, wrong-passphrase rejection, metadata-only storage inventory, explicit fill with zero POST submissions, deletion, and signed-out rejection. It uses disposable values with no provider authority.

Next: selective CSV import (not shipped)

Keep the import hook absent in 0.3.5. A separate change should add Import CSV under Settings, enabled only for a live-connected, unlocked vault. Accept one explicitly selected Chrome/Brave password export via a local file input; never read browser password stores or request filesystem/password-manager authority. Parse quoted CSV locally with bounded file/row/field sizes and required url,username,password columns. Preview only origin and username, unchecked by default; reject unsupported origins using the existing crypto allowlist, invalid URLs and passkey records. Do not expand the allowlist as part of import. Deduplicate selected records by exact origin and username against decrypted existing records in popup memory; skip duplicates, never overwrite silently. Encrypt each selected record immediately with the existing encryptItem, then send only its ciphertext envelope through vaultRequest. Stop on lock, owner change or session loss; report per-record success without logging credentials. Clear file contents, preview and plaintext references on completion/lock/close. Show “Delete the exported CSV after importing” at selection and completion. Tests must cover quoted fields, malformed/oversized input, selective import, duplicates, interrupted partial imports and ciphertext-only network requests. Passkeys remain relationship metadata; never import private material.

0.3.6 state hierarchy and auto-lock

Connect uses the existing Persimmons browser session, never an account password entered into the popup. Sign-in supports browser username/password autofill and returns an already signed-in browser to the homepage. The vault passphrase is a separate, locally chosen encryption secret; account sign-in cannot recover it.

Before connection, only Connect and the sign-in fallback are shown. Connected but locked shows Unlock and collapsed auto-lock settings. Unlocked supported origins show saved accounts and explicit Save. Actions/Juice stays collapsed and is hidden until unlock on Teahub; its independent auth/server boundary is unchanged. The homepage prominently shows Extension connected, hides Get whenever the extension responds, and offers Update only when its numeric version is older than the built artifact. A failed version check never invents an available update.

Auto-lock defaults to On popup close. Optional 5 / 15 / 60 minutes are fixed maximum sessions starting at unlock, not sliding inactivity timers. Changing the setting locks immediately. A non-exportable AES CryptoKey lives only in the extension service worker's memory; no plaintext passphrase, vault key, or account is stored in local/session/sync storage. Existing local passphrase-wrapped key records retain their format. No vault key is synced, exported, or sent to a server.

A bounded 20-second extension API heartbeat keeps the worker available across popup closes; a deadline timer and checks on every operation enforce expiry. Restart, reload, permission removal, failed live owner/session validation, or unexpected worker shutdown fail closed (and can lock earlier). The popup checks status every ten seconds without clearing fields unless the session has ended. Each crypto operation revalidates the live owner. Internal ports accept only this extension's exact popup URL; the website's separate read-only handshake returns installation/version/connection only, never keys, accounts, or unlock state.

Tradeoff: anyone controlling the same unlocked browser profile can use Fill during the selected window. Choose popup-close locking on shared machines, or Lock vault before leaving. Browser-restart-only unlock is intentionally not offered: all retained sessions have a maximum sixty-minute deadline. There is no recovery when the passphrase or original profile is lost.

Retest: update the existing folder with the helper at https://persimmons.app/assets/update-extension.py, then Reload in chrome://extensions (keep the same profile/folder; do not uninstall). Open https://persimmons.app/ → sign in if needed → 🍊 → Connect. Confirm the homepage says Extension connected / Up to date and has no Get or Update CTA. On https://wp.phly.cloud/wp-login.php, unlock → Save for this site → enter disposable credentials → Save. Close/reopen with the default: unlock → select account → Fill (no submit). Choose Settings → Auto-lock settings → After 5 minutes → unlock → close → reopen: still unlocked. Lock explicitly or wait five minutes: Fill is blocked. Unlock → select → Delete.