Repository navigation
Description
Filing gate: (a) a named producer, from a code reading. reach: exception: security (a possible exposure past an admin's off switch), so the first acceptance line is a walk. It comes from #23661's dev report 6194916763, out_of_scope_findings[0]. PR #23687 fixes the same defect on the other kernel shape and leaves this one unchanged on purpose.
- Reader who acts: triage, to grade and route.
Filed by domain:engine seat 2 · os-steve · session session_01ADzJtzYTLUfgrRZHxkagkX. ⛔ Not a claim.
What is measured next door (#23661, PR #23687)
On a kernel whose ObjectQLPlugin has no environmentId, the dev walked a real MCPServerPlugin stdio session:
- The gate reads the service's copy. The MCP stdio door gates every data verb on
(await metadataService.getObject(object)).enable.apiEnabled(enforceApiExposure,packages/mcp/src/stdio-data-bridge.ts). - An absent copy falls open. The gate reads an absent copy as "no declaration" and lets the verb through. That fall-open is by design: it matches the HTTP gate and is pinned in its own suite.
- Objects created after boot had no copy. So
query_recordswas served on an object saved withapiEnabled: false. - The same gap reached HTTP. The runtime dispatcher's gate (
refuseUnexposedDataAction, under the HTTP MCP data verbs) answered ALLOWED on that object.
PR #23687 keeps the copy of a runtime-authored object current on that kernel shape, through onMetadataMutation. Its followRuntimeObjectCopy returns early when environmentId is set.
The reading for this card (not walked)
- A kernel built with an
environmentIddoes not run the boot bridge. That isos devasenv_localand the cloud single-environment runtime. - Its
metadataservice is sourced from the artifact. By the objectql/mcp: the boot bridge's copy of each runtime object in the metadata service is never updated, so MCP stdio's enforceApiExposure keeps serving an object whose API exposure was turned off after boot, and lists a deleted one, until a restart #23661 dev's code reading, a runtime-authored object (saved through Studio, the REST metadata door or an AI author) has no copy in that service either. - If so, the same gate falls open.
enforceApiExposurewould read that object'sapiEnabled: falseas no declaration, and so possibly wouldrefuseUnexposedDataAction. MCP would then serve the object's data past the admin's off switch.
Wanted
- The walk first. On an
environmentIdkernel, save a runtime object withapiEnabled: false, or turn it off after a save, then call a data verb through the MCP stdio door and through the HTTP MCP data verbs. Record each answer. - If reachable, the exposure gates follow the object's current definition on this kernel shape too. Two directions, measured against each other in the PR:
- the copy is kept current, as fix(objectql): keep the metadata service's copy of a runtime-authored object current after boot (MCP stdio exposure gate) #23687 does on the other shape;
- or the gates read the registry or the stored definition instead.
- Out of scope here. Changing the gates' fall-open design for an absent definition is a separate, maintainer-level question. objectql/mcp: the boot bridge's copy of each runtime object in the metadata service is never updated, so MCP stdio's enforceApiExposure keeps serving an object whose API exposure was turned off after boot, and lists a deleted one, until a restart #23661's dev raised it (keep fall-open, HTTP parity; the seat answered keep). This card does not decide it.
- ⛔ No consumer-side tolerance.
Dedupe
Searched environmentId kernel runtime-authored object no metadata service copy enforceApiExposure apiEnabled MCP: 4 hits. #23661 is the other kernel shape. #23528 is the /mcp gate's missing error code. #22220 and #20071 are closed and unrelated.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 15, 2026
Triage: first grade ·
bug·security·priority:p2·domain:engine·pm:queue· measure first · land after PR #23687 · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-15T15:54ZRead fresh. On a kernel with an
environmentId(os devasenv_local, and the cloud single-environment runtime), the boot bridge does not run, and themetadataservice is sourced from the artifact. By code reading, a runtime-authored object therefore has no copy there. MCP'senforceApiExposure(and possiblyrefuseUnexposedDataAction) reads an absent copy as "no declaration" and lets the verb through.apiEnabled: falsemay be served through MCP.Grade p2 +
security, measure first (as #23661 was graded).apiEnabled: falseobject through MCP stdio or the HTTP MCP data verbs, the dev records it, this card goes to p1, and the round continues to the fix.Lane
domain:engine(the metadata-service copy). The gate half inpackages/mcp/packages/runtimerides on the cross-lane path if the measured fix is "the gates read the registry".Order: it lands after PR #23687 (#23661, open). Use its
onMetadataMutationfollow, or measure the registry-read alternative against it.findingleaves on grading.objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 2 · 2026-10-15T18:07Z
Session:
session_01GP3comALHRcRBixS2TEnLF(the dispatching seat; the dev runs as its own cloud session, named in the dispatch comment)Account:
sunhaolin(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-23689-env-kernel-runtime-object-exposureWorktree:
objectstack-issue-23689Domain:
domain:engineSeat:
domain:engine#1(seat post #6367)File surface: measure first. If the walk is reachable:
packages/objectql/src/plugin.ts, the runtime-object metadata-copy follow only (followRuntimeObjectCopyand itsonMetadataMutationsubscription, from PR #23687), extended to a kernel with anenvironmentId. The boot start-order region (PR #23694, #23649) is ⛔ not this card's. Plus real-kernel and door pins, and one changeset.changeset/23689-*.md. If the measured fix is instead "the gates read the registry or the stored definition", the gate halves are cross-lanedomain:cli(packages/mcp/src/stdio-data-bridge.tsenforceApiExposure, andpackages/runtime'srefuseUnexposedDataAction): stop and report that route with its measurement before writing it. Stop on any other breach and explain it in the report.Container & model:
M,mode:cloud(the maintainer's order on seat post #6367: 「后续 重活开发 都可以单独开云卡」),model: TIER_DEFAULT(this run'sdispatch-gates.mjs --tier: "no path-derived mandate"; a security walk first, then a copy-follow on a second kernel shape)Clause-②: no
Responsibility: the risk is produced by
packages/objectql's metadata-service copy of a runtime-authored object on a kernel with anenvironmentId(by code reading: no boot bridge, an artifact-sourced service, and PR #23687's follow returns early there), so MCP's exposure gate would readapiEnabled: falseas no declaration | no platform path covers it: PR #23687 covers the kernel shape withoutenvironmentIdonly | an MCP stdio session or the HTTP MCP data verbs onos dev(env_local) or the cloud single-environment runtime reach it (not walked; #23661's report 6194916763out_of_scope_findings[0]).Thread-read: 6196324526
Serial constraints cleared: the ordered-after PR #23687 (#23661) is on
main(3bc407d30). This seat's PR #23694 (#23649) touchespackages/objectql/src/plugin.ts's start order and the pre-sync event, a different region, and is landing now. Whichever lands second mergesmain. No other open PR or claim touches the copy follow.security: if the walk serves data from anapiEnabled: falseobject, the card goes to p1, as triage 6196324526 set.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Dispatch ·
domain:engineseat 1 ·session_01GP3comALHRcRBixS2TEnLF· 2026-10-15T18:09Z:mode:cloud, the dev sessionsession_01QBn4EzKV17Y1Ero6KDNZ45, titled "⚡ engine #23689 env-kernel runtime object MCP exposure (security, walk first)". It runs at the default tier on branchclaude/issue-23689-env-kernel-runtime-object-exposure, the claim 6198937330.environmentIdshapes:os devasenv_local, and the cloud runtime's composition. The doors are MCP stdio and the HTTP MCP data verbs. The dev posts aWalk readingcomment here before any code changes. If data from anapiEnabled: falseobject is served, this seat moves the card to p1, per triage 6196324526.domain:cli, and the dev stops and reports before writing them.Fixes #23689, then anos-dev-reportcomment here. If the walk is not reachable, apremise_still_valid: falsereport is the legal outcome.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Walk reading
Reachable: yes, on both
environmentIdshapes and through both MCP doors. An object whose API exposure is off is served, read and write, through the MCP stdio door and the HTTP MCP data verbs. The REST data door on the same boot refuses it. Per triage 6196324526, this is the stop condition: the card goes to p1 (the seat's label, not mine). The round continues to the fix.dev session
session_01QBn4EzKV17Y1Ero6KDNZ45· branchclaude/issue-23689-env-kernel-runtime-object-exposure· baseorigin/main69351e985a· 2026-10-15T18:23ZThe two kernel shapes measured
os devasenv_local, the real CLI boot.examples/app-crm,objectstack dev --fresh -p 38917from the built CLI at base.os devtakesresolvedMode = 'standalone'(serve.ts), so the kernel is composed bycreateStandaloneStack(packages/runtime/src/standalone-stack.ts):MetadataPlugin({ environmentId, artifactSource: local-file })plusObjectQLPlugin({ environmentId: 'env_local', hydrateMetadataFromDb: true, runPlatformMigrations: true }).apps/objectos-ee) lives inobjectstack-ai/cloud, outside this session's repository scope, so it was not booted. In this repo the shape is the oneObjectQLPlugin's own docblock names for it (environmentIdset,hydrateMetadataFromDbon, an artifact-sourcedmetadataservice), andcreateStandaloneStackcomposes exactly that with anyOS_ENVIRONMENT_ID. Measured in-process withcreateStandaloneStack's two plugin options verbatim andenvironmentId: 'env_01walkcloudshape', beside the same harness atenv_local.The in-process harness is PR #23687's door harness with the kernel swapped: a real
ObjectKernel, the realMetadataPluginandObjectQLPluginas above over an in-memory driver, and the realMCPServerPlugin, whosestart()builds the stdio data bridge (enforceApiExposure), driven by JSON-RPCtools/call. The one faked seam is thesys_api_keyrow the stdio principal resolves from. The HTTP leg in-process callsrefuseUnexposedDataAction(the gatecallDataasks first under the HTTP MCP data verbs) with this kernel's ownmetadataservice.Readings: real
os dev(env_local), HTTP MCP doorPOST /api/v1/mcp, signed in as the seeded admin(a) Created with
apiEnabled: false(PUT /api/v1/meta/object/qa_walk_gadget, answeredsuccess: true):(b) Saved exposed, a row written through REST, then exposure turned off (
qa_walk_toggle):Readings: in-process, identical at
env_localand atenv_01walkcloudshapeWhat the readings say about the mechanism
followRuntimeObjectCopyreturns at its first line whenenvironmentIdis set.metadataservice holds no copy of any runtime-authored object, exposed or not: the boot bridge does not run, and nothing else writes one. So every runtime-authored object is invisible tolist_objects/describe_object, and every one falls open at both gates.refuseUnexposedDataActionreads the same service and answers ALLOWED.MetadataManager.registerInMemorysets the in-memory map only (no loader, no announce), and itsunregisterdeletes throughdatasource:loaders only. That is the same bound PR fix(objectql): keep the metadata service's copy of a runtime-authored object current after boot (MCP stdio exposure gate) #23687 used, so the fork in the dispatch's stop clause does not come up.Next
The fix is at the producer, PR #23687's seam, on this kernel shape too: the copy follow stops returning early on an
environmentIdkernel, and a boot pass copies only the runtime-authored (tenant-authored) objects this kernel's registry holds. Code packages stay out of it. The full process-wide boot bridge stays gated (its cross-project-leakage reason is unchanged). The registry-read alternative for the gates is measured against it in the PR. Nothing is written inpackages/mcporpackages/runtime.objectstack-fleet commentedon Oct 15, 2026
Grade change ·
domain:engineseat 1 ·sunhaolin·session_01GP3comALHRcRBixS2TEnLF· 2026-10-15T19:12Zpriority:p2→priority:p1, under the stop condition triage set (6196324526): "if the walk serves data from anapiEnabled: falseobject through MCP stdio or the HTTP MCP data verbs, the dev records it, this card goes to p1, and the round continues to the fix."The dev's
Walk reading6199252355 records exactly that.os dev(env_local), real CLI boot,examples/app-crm: an object saved withapiEnabled: falseis read and written through the HTTP MCP data verbs, while REST answersOBJECT_API_DISABLED.env_localand at a cloud-shapedenvironmentId, the stdio bridge and the HTTP gate both answer ALLOWED.The round continues to the fix at the producer, as dispatched (6198975590). ⛔ The fall-open design for an absent definition is unchanged. The cloud host in
objectstack-ai/cloudwas not booted, and the PR says so.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{ "issue": 23689, "status": "done", "branch": "claude/issue-23689-env-kernel-runtime-object-exposure", "pr": "https://github.com/objectstack-ai/objectstack/pull/23716", "session": "session_01QBn4EzKV17Y1Ero6KDNZ45", "premise_still_valid": true, "summary": "Walk first (comment 6199252355, posted before code): REACHABLE. On the real `os dev` (env_local, createStandaloneStack composition) the HTTP MCP door served query_records and create_record (a row written) on an object saved with apiEnabled:false and on one turned off after a save, while REST refused both with OBJECT_API_DISABLED. In-process, with createStandaloneStack's plugin options at env_local and at a cloud-shaped env id, the stdio door and refuseUnexposedDataAction did the same. A new case (d), saved off and then restarted, was also served. H1-H3 hold; on this shape the metadata service held NO copy of ANY runtime-authored object. Per triage, that is the p1 stop condition; the seat relabels. Fix at the producer only (packages/objectql/src/plugin.ts): followRuntimeObjectCopy's environmentId early return is removed, and a kernel with environmentId plus hydrateMetadataFromDb copies its hydrated tenant-authored objects once at boot (bridgeRuntimeObjectsToMetadataService, registerInMemory only, code packages untouched, full bridge still gated). After-walk on a real os dev: refused ('object is not exposed via the API'); the exposed CONTROL is served and now listed; deleted gives OBJECT_NOT_FOUND. Nothing was written in packages/mcp or packages/runtime. Card assignee not written; PR assignee sunhaolin.", "tests": "At 94604ee51. `pnpm --filter @objectstack/objectql test`: 426 files / 8145 tests passed. `pnpm --filter @objectstack/objectql typecheck`: exit 0. `pnpm --filter @objectstack/dogfood typecheck`: exit 0, 0 errors (after a forced closure rebuild with declarations). plugin-runtime-object-metadata-copy.test.ts: 10 passed (5 new environmentId-shape pins). mcp-stdio-runtime-object-exposure.test.ts: 10 passed (describe.each over the no-environmentId and env_local shapes, plus a new restart case; run against rebuilt objectql dist). Gate suites: mcp stdio-data-bridge.exposure plus plugin.record-resource-exposure 35 passed; runtime action-execution-calldata-relation-target-exposure 13 passed. Ablations, with every mutation proved on disk by scripts/ablation-replace.mjs (anchor 1 to 0, blob changed) and every restore proved (blob == HEAD 3751a6c6161c, git diff HEAD empty). objectql leg 1, the early return put back: 4 failed / 6 passed. Leg 2, the boot pass call removed: 2 failed / 8 passed. Door, mutant dist (both mutations, objectql rebuilt, preflight marker present in 2 built files): the env shape 5 failed, the no-env shape 5 passed (CONTROL that the #23687 shape is unchanged). Door, restored (rebuilt, preflight --absent from all 14 built files): 10 passed. The first door attempt was void: its deletion broke the .d.ts build before any test ran, so it was redone with a compile-safe mutation. Walk before and after on the real os dev HTTP MCP door, as in the PR body.", "gates": "Local: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at 94604ee51 (no paths, 4 paths against merge base 69351e985) derives 68 families. All 68 were run with exit codes captured before any pipe; 64 exited 0 on the first pass. Re-run to exit 0: check-engine-split-ratio --days 90 (exit 2, shallow refusal) and check-plugin-teardown-shape --self-test (exit 3, shallow), both after `git fetch --shallow-since=2026-07-10 origin main`; check:dts-closure (exit 1) and check:dual-build-cjs-loads (exit 3), whose missing .d.ts and builds came from my own walk-time OS_SKIP_DTS CLI build, both after `turbo run build --force` (175/175 declaration files; 68 packages probed). `dispatch-gates --ran`: 68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN, exit 0; the re-derived list is byte-identical. CI on 94604ee51 when this was written: 24 check-runs. 'No other open PR may claim the same issue' succeeded and 'Packed-tarball smoke (opt-in)' was skipped. The rest, including Lint, Type Check, Governed Surface Queue Guard and Check Changeset, were in_progress; Test Core and Build Core were not yet listed. CI convergence belongs to the PM.", "mcp_calls": "4, all read-only: mcp__github__issue_read (get #23689, get_comments #23689), mcp__github__pull_request_read (get #23694, get_check_runs #23716)", "api_writes": "2 relay strokes before this comment, plus this report comment = 3, all through scripts/pm as objectstack-fleet[bot]. (1) post-stamped --comment=23689 for the Walk reading, comment 6199252355, run 38785167313. (2) fleet-write/dispatch.mjs pr_create, draft, with the assignees leg (POST pulls, then POST issues/23716/assignees), run 38792508250. (3) post-stamped --comment=23689 for this os-dev-report. Labels: 0 writes (none named by the dispatch; the PR carries a changeset, so skip-changeset does not apply). git push is not counted here.", "open_questions": [ { "question": "The dispatch forbade touching ObjectQLPlugin.start()'s boot order. The boot half of the fix is one `else if (this.hydrateMetadataFromDb)` branch on the existing bridge gate, after hydration. No step moved, and it is disjoint from PR #23694's pre-sync region. Is that inside the bound?", "options": ["A: accept, since no order changed and the region is disjoint", "B: move the boot copy elsewhere, e.g. a kernel:ready hook (later than MCP's start, so it would leave a window where the gates fall open)"], "recommendation": "A. Measured reading (d) shows the boot copy is required, and the start() spot is the earliest point after hydration, before any door serves." }, { "question": "The cloud single-environment host itself (objectstack-ai/cloud apps/objectos-ee) is outside this session's repo scope and was not booted. Its shape was measured through createStandaloneStack's options with a non-env_local environment id, as named in ObjectQLPlugin's hydrateMetadataFromDb docblock. Does the seat want a cloud-side confirmation?", "options": ["A: accept the in-repo shape reading", "B: a cloud-lane walk after this lands"], "recommendation": "A, plus B only if objectos-ee composes ObjectQLPlugin without hydrateMetadataFromDb. In that case the mutation follow still covers saves, but hydrated objects (reading d) need the boot pass's condition revisited." }, { "question": "Clause-②: the claim says `no`, and the changeset and PR body both carry `Clause-②: no`. The refusals bring both gates to apiEnabled:false's declared contract, and no published accept set narrows. On this shape list_objects/describe_object now also name exposed runtime objects, which is the same defect's other symptom.", "options": ["A: no", "B: no (narrowing)"], "recommendation": "A, as declared." }, { "question": "Role-file deviation: in mode:cloud the primary checkout was already on the branch. I detached it to origin/main to create the dedicated worktree, as the worktree-first rule requires, and put it back on the branch at the end.", "options": ["A: noted"], "recommendation": "A" } ], "out_of_scope_findings": [ "carrier: #23528 · the HTTP MCP door's exposure refusal reaches the client as a bare 'object is not exposed via the API' with no error code (seen in the after-walk); #23528 already tracks the /mcp gate's missing error code · noted, not filed", "carrier: none · the real-CLI stdio door was not spawned (its transport would share os dev's stdout with the banner); the stdio leg is the in-process MCPServerPlugin over createStandaloneStack's options · noted in the PR's Acceptance notes" ] }objectstack-fleet commentedon Oct 15, 2026
ACCEPT → PR #23716 comment 6201510767 ·
domain:engineseat 1 ·session_01GP3comALHRcRBixS2TEnLF· 2026-10-15T20:22Z. Contract review 6201494120 PASS at94604ee51b(TIER_DEFAULT, the seat's review of record for thissecurityp1 card). Landing follows through the merge queue. The dogfood door pin first runs inmain's full battery after the merge, and this seat reads that run.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed ·
domain:engineseat 1 ·session_01GP3comALHRcRBixS2TEnLF· 2026-10-15T20:56Zpr_ready+automerge_enablethrough the fleet-write relay (run 38801521390). The PR event recorded the enqueue at 20:27:21Z. After the merge,git ls-remotefinds nogh-readonly-queuebranch for PR #23716.origin/main76560a1d84is onorigin/main(git merge-base --is-ancestorexit 0). It is the squash of PR #23716: the same 4 files, +263/−12. The merge event fired at 20:55:28Z.Status: #23689 is closed, and its
pm:dispatchedis removed in this stroke. The cloud dev sessionsession_01QBn4EzKV17Y1Ero6KDNZ45is archived.Open watch:
packages/qa/dogfood/test/mcp-stdio-runtime-object-exposure.test.tsskips on pull requests by design. Its first CI run ismain's next full battery containing76560a1d84. This seat reads that run and records it here. If it is red, the seat reopens this card.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Watch closed ·
domain:engineseat 1 ·session_01GP3comALHRcRBixS2TEnLF· 2026-10-15T21:56ZThis closes the open watch from the landing record 6202106850.
main's CI run 38806610561 (scheduled 21:01Z, atf40d877f81, which contains PR #23716's76560a1d84) concluded success: 17 of 17 jobs. That includesDogfood Regression Gateshards 1–3 and its aggregate, andDogfood Verify CLI. The shards carrypackages/qa/dogfood/test/mcp-stdio-runtime-object-exposure.test.ts, the door pin that skipped on the pull request. Its first CI run is green. The card stays closed.Generated by Claude Code