Archive: the extension lives in persimmons/app/extension

This commit is contained in:
teahubxyz committed 2026-10-14 22:10:38 +00:00
1 parent 0e92a80b20
commit aece02b456
1 file changed
+2 -293
+2 -293
View File
@@ -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.