diff --git a/README.md b/README.md index c2c607c..72b3fed 100644 --- a/README.md +++ b/README.md @@ -1,294 +1,3 @@ -# Current dedicated human-vault taste test +# persimmons/extension (archived) -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](../docs/persimmons-host-receipt.md). - -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](https://developer.chrome.com/docs/extensions/develop/concepts/activeTab) -and [extension host permissions](https://developer.chrome.com/docs/extensions/develop/concepts/network-requests). - -## Verification - -From repository root: - -```sh -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](https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Turning_off_form_autocompletion), -[Chromium icon packaging](https://developer.chrome.com/docs/extensions/develop/ui/configure-icons). - -## 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: - -```sh -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](https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world) -and [update lifecycle](https://developer.chrome.com/docs/extensions/develop/concepts/extensions-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](https://developer.chrome.com/docs/webstore/cws-dashboard-distribution) -and [updating a listing](https://developer.chrome.com/docs/webstore/update). - -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](https://developer.chrome.com/docs/extensions/reference/manifest/key). - -### 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. +The Persimmons Chromium extension lives in [`persimmons/app`](https://teahub.xyz/persimmons/app) under `extension/`, with its history. The packaged ZIP is published at `https://persimmons.app/assets/extension.zip`. This repository held an older copy and is archived.