Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions PRODUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Product

## Register

product

## Users

An individual owner keeping personal secrets on their own Mac or Windows computer. The owner needs to find a Note, deliberately reveal a Hidden Field, save changes, make a Backup, or recover access without a server account. Use the established names in [CONTEXT.md](CONTEXT.md) and explain actions in plain language.

The optional Agent Companion serves a separate workflow: an owner gives tools in a distinct standard Mac account one restricted OpenAI text operation using a separately created Agent Key. An agent has no app interface to the Vault. The first private Agent Key entry flow is Mac only.

## Product Purpose

Keep Notes encrypted locally and make access, locking, saving and recovery understandable. The Vault and its WebView remain offline. The optional companion's fixed network operation belongs to its separate sandbox and never receives Vault content or metadata.

Success means the owner can tell what an action will do before choosing it, what actually happened afterward, and how to cancel or recover. A local readiness check, an encrypted save, a running grant, provider restrictions and a working ChatGPT connection are different facts; the interface must identify each accurately.

## Brand Personality

Calm, direct, careful. Preserve the existing desktop utility's familiar system controls, concise labels and restrained Settings hierarchy. Use functional explanations and specific limitations instead of promotional language or absolute security claims. The existing app is the visual reference; no external brand reference has been supplied.

## Anti-references

- Marketing heroes, decorative photographs, repeated feature-card grids and oversized display typography inside the app.
- Decorative glass, gradients, pulsing requests, animated urgency and celebratory effects around sensitive actions.
- Cryptic technical errors, unexplained disabled controls, vague success messages and claims of protection that have not been observed.
- Custom controls that make ordinary keyboard, focus or cancellation behavior unfamiliar.

## Design Principles

1. **Keep the owner in control.** A request may produce a fixed notice. Opening private entry, saving or replacing a key, and starting a companion grant each require their own appropriate local action. Do not steal focus or start work automatically after saving.
2. **Make state truthful.** Show waiting, progress, cancellation, failure and completion in words. Distinguish local storage from provider validity and connection readiness. Report unverified behavior as unverified.
3. **Keep sensitive entry native.** An Agent Key belongs in the dedicated native secure control, never in chat or a WebView form. Status, errors, help and accessibility descriptions must not disclose its value or value-derived metadata. Vault credentials and content remain outside the companion workflow.
4. **Preserve familiar interaction.** Reuse the existing system font, semantic colors, grouped Settings rhythm and native controls. Keep active status and Stop visible, preserve focus across updates and keep the action hierarchy compact.
5. **Make interruption safe and understandable.** Cancel and Escape are ordinary outcomes. Focus loss and lifecycle changes invalidate private work. A late callback cannot revive a cancelled action, replace another request or restart a grant.

## Accessibility & Inclusion

Support keyboard-only use, visible focus, descriptive control labels and meaningful screen-reader status. Keep private input's native secure-control semantics without creating a plaintext mirror. Announce state changes without repeating unchanged polling results or moving focus.

Target at least 4.5:1 contrast for normal text, including placeholders and button labels, and 3:1 for focus and required non-text control indicators. Check actual supported light, dark, accent and Increase Contrast settings; system colors alone are not evidence of a passed check. Use words alongside semantic color. Respect reduced motion and keep actions reachable at the minimum supported window size and larger text settings.

These are implementation and acceptance requirements, not a claim of completed accessibility certification. Native authentication, secure accessibility, capture behavior and focus transitions need fictional-data physical checks in addition to source and Web fixture checks.

## Context and Current Scope

This document records existing product direction from the README, CONTEXT, native credential design, committed styles and the owner's approved private Agent Key design. It does not change the security policy in AGENTS.md or the ADRs.

The approved private entry flow is still being implemented. Existing plaintext companion setup must not be described as encrypted custody. New encrypted readiness and ChatGPT completion copy may ship only with the corresponding implementation and evidence. Source-based UI guidance and acceptance details are recorded locally in `.scratch/private-agent-key-entry-20261104/UI-AUDIT.md`.
6 changes: 5 additions & 1 deletion companion/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ The selected deployment is: agent/code → stdio bridge → local owner broker

The bridge lists the tool even while the broker is off and reads the token again on each call, so a new Start needs no restart of the agent's MCP client. With no token or no listener, a call returns one fixed error telling the agent to ask the owner.

Both protected launchers bind the real loopback listener before publishing a fresh random 32-byte base64url token with the agent's read ACL. A failed bind leaves any existing launch's token untouched. The standalone launcher also rotates the token on every launch, without extending the reviewed plan's grant deadline. Cleanup uses the created file's inode/device lease, retains its open handle until cleanup, and removes only that publication; stale cleanup cannot remove a successor already published in the owner-controlled handoff directory. During startup, provider admissions remain disabled until publication protection fully validates and the grant is rechecked. Shutdown disables admissions and cancels active calls first, then keeps the listener bound through owned token cleanup before releasing the port. The same ordering covers failed publication and startup expiry, so a cooperating restart cannot publish a successor during the old cleanup. Standard close callbacks and repeated close calls retain their behavior.

This closes the demonstrated capture-before-bind and later-token-reuse path. The bearer transport does not authenticate the server to the client; it is not complete endpoint authentication or protection against malicious software running as the owner.

[The keyless code example](docs/keyless-code.md) shows the client call and its prerequisites. This is one restricted operation, not a drop-in OpenAI SDK endpoint. The Mac identity boundary, provider controls and live connection remain unverified. The planned credential file format is plaintext with owner-only permissions, not encrypted Vault storage; no real credential file has been provisioned. OpenAI receives the key over HTTPS for authentication. Owner Vault and wallet keys are never part of this route.

## Fixed operation
Expand Down Expand Up @@ -104,7 +108,7 @@ The integrated entry point runs the same fixed broker with:

- fixed data paths inside the helper's own container (`$HOME/Library/Application Support/encrypted-note-companion/plan.json` and `credential.env`), never chosen by a caller;
- one new grant of at most one hour for each owner Start, enforced before the credential read, before listening and before every admission, including direct loopback calls;
- a fresh delegated agent token for each Start, written owner-only (0600, plus the single agent read ACL) into the owner-prepared handoff folder, and removed on every stop;
- a fresh delegated agent token for each Start, written only after binding loopback, owner-only (0600, plus the single agent read ACL) into the owner-prepared handoff folder; each stop removes only the publication owned by that launch;
- a bounded owner-control protocol on stdin/stdout, separate from the agent MCP channel: one `{"v":1,"op":"stop"}` command in; `ready`, `stopped`, `error` and `check` events with fixed codes out. Control EOF, a malformed command, a signal or the grant deadline stops the broker, and the process then exits without waiting for the helper to close the pipe. Nothing from the plan, credential, token, provider or MCP traffic is printed.

Its `--check` mode reports readiness booleans and never reads the credential's contents. Code and runtime ancestors may be admin-group-writable (an app in /Applications) only after the agent account is confirmed not to be an administrator.
Expand Down
6 changes: 3 additions & 3 deletions companion/isolation/PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The local Vault requires no new server login, OpenAI account, Apple Developer lo
6. Complete process, clipboard, capture/control and connected-host checks below. Fix failures only through a separately reviewed change; do not grant privacy/debugging permissions to make a denial check pass. If ownership is ignored on a volume or broad ACL changes seem necessary, stop and select a different deployment location.
7. Independently apply provider permission, model/tool denial, rate limits, one-hour expiry, $1 HARD monthly project limit and owner revocation controls. Spend alerts are insufficient. Hard-limit enforcement can slightly overshoot during propagation. The known provider hosted-shell scope gap remains explicit; raw-key custody must hold.
8. Only after boundary review, use the secure Platform picker, destination confirmation and trusted helper for the NEW Agent Key. Read no existing owner key. The selected file is parsed without eval, sourcing or environment export. The one key-paste route is write-agent-key.mjs, which refuses any value that isn't shaped like an OpenAI API key.
9. In the owner session, explicitly launch owner-launcher.mjs with the reviewed plan path, confirmed credential-file path and delegated-token-file path. These arguments are paths only. The launcher binds loopback, holds the key in owner-process memory, and stops when the plan's grant ends (at most one hour). JavaScript does not guarantee memory erasure. Alternatively, encrypted-note's integrated helper runs isolation/integrated-broker.mjs: the owner installs the plan and confirmed credential through Settings → Agent Companion → Set up…, and each Start issues a one-hour grant and a fresh delegated token in the handoff folder (encrypted-note docs/agent-companion.md).
9. In the owner session, explicitly launch owner-launcher.mjs with the reviewed plan path, confirmed credential-file path and delegated-token-file path. These arguments are paths only. The launcher binds loopback before publishing a fresh random delegated token for that launch, holds the key in owner-process memory, and stops when the original plan's grant ends (at most one hour). It removes only its own token publication when the listener closes; restarting does not renew the grant or accept the prior launch's token. JavaScript does not guarantee memory erasure. Alternatively, encrypted-note's integrated helper runs isolation/integrated-broker.mjs: the owner installs the plan and confirmed credential through Settings → Agent Companion → Set up…, and each Start issues a one-hour grant and a fresh delegated token in the handoff folder (encrypted-note docs/agent-companion.md).
10. In the actual agent session, configure the MCP client to launch agent-bridge.mjs with only the nonsecret plan path. No key or token value belongs in MCP configuration. Make one owner-approved fictional text request. Verify expiry, revocation and provider denial before recommending real use. Stopping the broker is not provider revocation.

No launchd plist, login item, automatic restart, sudoers rule, public listener, custom user-login service or account-creation script is installed. The broker is an explicitly started local process and can be stopped with Control-C in the owner terminal.
Expand All @@ -41,11 +41,11 @@ Use a trusted absolute Node executable; replace paths locally without putting ac
- fixtures.mjs prepare takes the reviewed plan path and an existing protected parent; it creates only a fresh enote-isolation-* directory with 0700 directories and 0600 fictional members. Its output is a nonsecret private fixture manifest, never a GitHub artifact.
- agent-probe.mjs takes the reviewed plan, copied nonsecret fixture manifest and a new private agent workspace. It checks actual UID/name, privileged groups and noninteractive sudo before touching fixtures. It then checks its own workspace positively and requires permission denial for owner read/list/write/create/rename/unlink, fake code/policy/dependency writes and an agent-created symlink into the owner fixture.
- fixtures.mjs verify-owner takes the original owner-record.json and requires unchanged fixture ownership, modes and hashes. Preserve evidence on failure. Remove only these explicitly created disposable members after owner verification; no broad recursive cleanup or permission command is provided.
- owner-launcher.mjs takes reviewed plan, confirmed credential path and delegated-token path. It requires the owner UID and the token path to match the plan. Application/SDK imports are delayed until protection checks finish, but an independent trusted preflight is still mandatory.
- owner-launcher.mjs takes reviewed plan, confirmed credential path and delegated-token path. It requires the owner UID and the token path to match the plan, checks the owner-prepared handoff directory, and creates a fresh token only after successfully binding the listener. A failed bind leaves an existing publication untouched. Provider admissions stay disabled until publication fully validates and the original grant is rechecked. Shutdown and failed publication disable admissions, cancel active calls, and clean only that launch's publication while keeping the listener bound; only then is the port released for a successor. Application/SDK imports are delayed until protection checks finish, but an independent trusted preflight is still mandatory.
- write-agent-key.mjs takes one output path and reads the NEW Agent Key on stdin (`pbpaste | node write-agent-key.mjs ~/enote-agent-key.env`), never as an argument. It writes only a value shaped like an OpenAI API key (sk-, then 37 to 509 letters, digits, - or _; no spaces or line breaks). It refuses Admin keys (sk-admin-), Anthropic (sk-ant-) and OpenRouter (sk-or-) keys, and input over 4096 bytes however the reads split it; an OpenAI key copied from the Vault would still pass. It writes owner-only 0600 without following a symlink or writing through a hard link, refuses a file with an ACL allow entry (set on it or inherited from its folder, checked before and after writing), then asks Time Machine to leave the file out (tmutil addexclusion), which may fail without stopping it. No message includes the value.
- agent-bridge.mjs requires the actual selected Standard UID. It reads only the plan and the separate delegated token and exposes stdio to the local MCP client. It forwards only the one fixed bounded operation. It lists the tool even while the broker is off and reads the token again on each call, so a new Start needs no restart; with no token or no listener, a call returns one fixed error.

The delegated token may need a fresh directory under /Users/Shared if the owner home is not traversable. This exception is only for the limited local token, never the provider key. Require root-owned sticky /Users/Shared, a new owner-owned 0700 child, exactly one agent-user search ACL on that child and exactly one agent-user read ACL on its 0600 token file. Other allow ACLs fail closed. Never weaken an existing owner parent to make token sharing work. An owner-reviewed targeted token-creation/ACL step remains pending; this package executes none.
The delegated token may need a fresh directory under /Users/Shared if the owner home is not traversable. This exception is only for the limited local token, never the provider key. Require root-owned sticky /Users/Shared, a new owner-owned 0700 child, exactly one agent-user search ACL on that child and exactly one agent-user read ACL on its 0600 token file. Other allow ACLs fail closed. Never weaken an existing owner parent to make token sharing work. An owner-reviewed targeted handoff-directory preparation step remains pending; this package executes none. Each protected broker launch creates its own 0600 token and adds the exact agent read ACL after binding. Cleanup holds an inode/device lease with an open file handle, and the protected HTTP close lifecycle reserves the listener until that cleanup finishes, so cooperating launches cannot publish a successor between the ownership check and unlink. Stale cleanup also leaves an already-replaced publication untouched. These lifetime checks close capture-before-bind and token reuse; the bearer transport does not authenticate a server to its client or protect against malicious software running as the owner.

## Additional fictional checks and stop rules

Expand Down
Loading
Loading