Repository navigation
Description
Problem
A native family app needs notes and other authored records to carry trustworthy per-member authorship when invoked through Executor. We could not establish a supported contract that binds an authenticated caller to the effective app invocation identity. Caller-supplied member IDs cannot be the authority, and saved execution profiles must remain distinct from people.
This is an integration-contract gap, not a demonstrated authorization vulnerability. If a supported account-binding pattern already satisfies it, document and prove that pattern instead of adding another identity mechanism.
Evidence
During CrommettCrew Phase 0 discovery on October 12, 2026 (PDT), the personal self-hosted instance reported apps@0.0.1-beta.74. Its published BoundContext exposed account bindings and services but no immutable caller identity. Local source inspected by that integration was a61cbe30cdd42402dce8fd7b2416575a2fdaa6a0; relevant SDK surface: packages/apps/src/contracts/app.ts (BoundContext). Local source is not proof of identical deployed behavior.
The integration kept real family profiles and notes disabled. A separate loopback-only canvas proof uses synthetic profiles and does not solve Executor identity.
Requested outcome
Provide and document either a supported per-member account binding or host-established immutable invocation identity. Keep Executor principal, application member, organization, and saved execution profile explicitly distinct. Define behavior for native requests, browser app tools, MCP, delegated calls, and background work wherever supported; unsupported contexts must fail explicitly rather than inherit operator authority.
Acceptance
- A real self-host E2E scenario uses two synthetic principals in one organization to invoke the same note query/mutation and records the correct authenticated author.
- Substituting member, organization, or profile identifiers cannot impersonate another principal or expand authority.
- Revoked membership/sessions and hidden or unselected tools fail closed; adding a privileged capability does not silently grant it to existing selections.
- The public SDK and authoring documentation explain the supported identity binding and its limitations, with an executable example.
- Tests drive public HTTP/MCP/browser surfaces under
e2e/, following repository conventions.
Related: native-to-browser session exchange is tracked in #16. This issue does not authorize enabling production family data or deployment.
Activity
shuv1337 commentedon Oct 23, 2026
Status 2026-10-23 (post-cutover audit): mostly delivered; three acceptance items are still unproven, so this stays open.
Delivered and live (deployed
cff076a98;ctx.caller/ctx.approvedBysince #27, behind thecallerIdentitycapability):ctx.caller(kind,subject,issuer,email,organizationId,channel, provenance) is set by the host and never by the caller. Documented with an example inpackages/app-templates/executor/skills/app-authoring/tools.md("ctx.calleris the invocation's verified caller…").e2e/tests/caller-identity.spec.ts(callerIdentity) has a member and an admin, each over MCP with a PAT, call the same tool and get distinct verified callers. It also covers the dashboard person and workflow runs (system caller, owner subject), caller-scoped cache, no secret in caller data, and an approval recorded asapprovedBywhile the requester stays the caller. Revoking one user's key stops only that user.personApprovalin the same file: a tool that requires a person refuses the agent's own approval. Per-caller identity also holds in a shared runtime (shared-runtime.spec.ts), and app-page calls have theuichannel (app-page-approvals.spec.ts).ctx.caller/ctx.approvedByfor its operator and console gates.Not yet proven by a scenario:
ctx.caller, but no scenario sends forged fields to show it.Possibly out of scope: the native-request context overlaps #16, which is still open.
shuv1337 commentedon Oct 23, 2026
Status 2026-10-23 01:15 PDT: #58 merged (
cbaeeb998). Every acceptance item except two is now proven by a scenario, so this stays open.New in #58 (test-only):
caller-identity.spec.ts, scenario "apps see each MCP user, the dashboard person and workflow work as distinct verified callers" (callerIdentity). Its test app adds aclaimtool whose input takescallerandapprovedByas plain JSON. Alice calls it naming Bob's caller withorganizationId: "org_forged"and an owner email, plus anapprovedBynaming Bob as a human browser approver. The scenario assertsforged.callerequals Alice's own verified caller andforged.approvedByisnull. So member, organization and approver identifiers in the input can't impersonate another principal.Existing scenarios that cover the rest of acceptance item 3:
member-api-keys.spec.ts, "Organization membership removal permanently revokes pinned API keys" (memberApiKeys), step "Membership lifecycle". AfterPOST /api/auth/organization/remove-member:warm-request-auth.spec.ts, "Dashboard requests refuse signed-out sessions and removed members immediately" (warmRequestAuth):remove-member.mcp-grant-restrictions.spec.ts, "MCP grants retain exact selections through deploys and live narrowing" (liveGrantRestrictions).latermutation after consent.laterand to a hidden app'sechoboth returnexecution.ok === false, while the selectedechosucceeds.echofails.Targeted run: the four scenarios above all passed on self-host E2E against the #58 head (
19d875d3c). On the fork, #58's CI rancheckand Socket, both green; it has no E2E jobs. Prod (a4f6f0b84, deployed 00:52 PDT) has the same app/runtime code; #58 changes only the spec.Still unproven, so this stays open:
runsAsaccount) to show it can't impersonate another principal. The member, organization and approver fields are covered by Prove that identity named in a tool's input never becomes ctx.caller #58.uichannel), MCP, delegated approval (approvedBy) and background work (workflow system caller) are covered.Merge pull request #60 from shuv1337/shuv/runsas-substitution-e2e-202…
shuv1337 commentedon Oct 23, 2026
Update 2026-10-23: saved-profile (
runsAs) substitution is now proven by #60 (merged as991910d29, test-only).#60:
mcp-connections.spec.ts, scenario "Scoped connections run apps only as the selected accounts and link bare accounts" (scopedConnectionAccounts).runsAsnames the owner's personal account.POST /api/organizations/:org/mcp-connectionsreturns 400ConnectionAccessInvalidwith reasonaccount(notapp, so app access passed and the account check refused it).linkedAccountIds(member)stays[].bun run checkpassed. Fork CI (check, Socket) passed with no review comments. The scenario's Cloud target runs in deployed tests.Already in the same scenario:
$accounts) fails withAccountChoiceUnsupported(400).Related scenarios:
scopedConnectionLinkSwappedMidExecution: an account swapped mid-execution is refused with a target refusal.hostedMcpLinkHeldAccess: when account or app access is withdrawn while a save waits, the save getsConnectionAccessInvalidand nothing is linked.Acceptance status. All five listed acceptance items now have passing E2E scenarios:
Not covered: native requests. The requested outcome also asks for defined behavior for native requests. There is no native-request surface yet; the native-to-browser session exchange is #16, which stays open and separate. I'm leaving this open for the owner to decide whether that part is satisfied by #16's tracking or must land first.