Context
Follow-up from the cross-family review of PR #13152. The PR shipped saved-layout helpers for Neo.dashboard.DockZoneModel and was approved as merge-eligible; the review surfaced one non-blocking security-contract watch item: the helper rejects known runtime/credential keys via a denylist and rejects non-JSON values, but free-form metadata can still persist a JSON string under a non-denylisted sensitive key such as apiKey, sessionKey, or authKey.
Release classification: post-release (Approve-as-is follow-up — non-blocking; #13152 remains merge-eligible without this).
Live latest-open sweep: checked latest 20 open issues immediately before creation; no equivalent dock saved-layout metadata-secret ticket found. A2A in-flight claim sweep: checked latest 30 messages; no overlapping [lane-claim] / [lane-intent] found. Local exact sweep: rg "dock layout|dock-layout|saved layout|saved-layout|dockLayout|metadata secret|apiKey|sessionKey|authKey|HarnessDockZoneModel|createSavedLayout|restoreSavedLayout" resources/content/issues resources/content/discussions; no equivalent ticket found. KB semantic sweep: ask_knowledge_base("dock layout saved layout metadata secrets createSavedLayout restoreSavedLayout HarnessDockZoneModel") confirmed HarnessDockZoneModel.md already documents that layout metadata must not contain secrets.
The Problem
The documented contract is stricter than the current mechanical guard. learn/agentos/HarnessDockZoneModel.md already states that saved-layout metadata is JSON-only descriptive data and must not contain credentials, PATs, access tokens, or harness bridge tokens. PR #13152 enforces part of that boundary by rejecting non-JSON values and known forbidden key names, but the free-form metadata object remains denylist-governed.
That means a value like {metadata: {apiKey: "sk-live-x"}} is JSON-serializable and can round-trip unless the key appears in the exact forbidden-key set. The issue is not that #13152 is unsafe to merge; metadata is operator-authored annotation, and the PR already blocks the obvious runtime/preview credential keys. The issue is that the persistence helper now represents a durable contract, and future storage/UI consumers will treat its output as safe unless the contract is enforced mechanically or narrowed explicitly.
The Architectural Reality
Neo.dashboard.DockZoneModel owns the pure saved-layout projection:
DockZoneModel.LAYOUT_SCHEMA defines neo.harness.dockLayout.v1.
createSavedLayout(document, metadata) wraps a validated dock-zone document into a saved-layout object.
restoreSavedLayout(savedLayout) fail-closes on malformed wrappers before returning a dock-zone document.
learn/agentos/HarnessDockZoneModel.md is the durable contract doc for dockZone.v1, dockPreview.v1, and dockLayout.v1.
The current contract doc already covers the no-secret metadata rule, so this ticket should not add a redundant doc-only restatement. The work belongs in the helper/test layer unless implementation discovers the doc wording needs a narrow sync.
The Fix
Choose one narrow contract shape and enforce it in src/dashboard/DockZoneModel.mjs with focused tests in test/playwright/unit/dashboard/DockZoneModel.spec.mjs:
- Prefer an allowlist for layout-wrapper metadata keys if the storage/UI leaf has a known small surface; or
- Extend the forbidden-key detection to cover common credential-key aliases such as
apiKey, sessionKey, authKey, and compound forms like apiToken, while preserving the current recursive JSON-only guard; or
- Explicitly narrow
metadata to documented non-secret operator annotations and fail closed on suspicious key names.
The implementation should keep the helper pure and storage-backend-free. It should not introduce a persistence service or UI in this ticket.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback / Edge Case |
Docs |
Evidence |
DockZoneModel.createSavedLayout(document, metadata) metadata handling |
learn/agentos/HarnessDockZoneModel.md saved-layout metadata contract; PR #13152 review finding |
Reject metadata that appears to carry credentials/secrets before producing a saved-layout wrapper |
Return {layout: null, errors: [...]} without mutating caller input; preserve existing non-secret metadata behavior |
Existing HarnessDockZoneModel.md contract already states no-secret metadata; update only if exact behavior narrows the documented contract |
Focused unit tests for suspicious metadata keys and allowed metadata |
DockZoneModel.restoreSavedLayout(savedLayout) metadata handling |
Same saved-layout contract |
Fail closed if a saved layout wrapper already contains suspicious secret metadata keys |
Return {document: null, errors: [...]}; never normalize/restore the unsafe layout |
Same as above |
Focused restore-side unit test |
Decision Record impact
none — this tightens the implementation to match the existing dock-zone contract; no ADR change.
Acceptance Criteria
Out of Scope
- Adding a storage backend for saved layouts.
- Building the dock layout UI.
- Reworking the dock-zone model schema beyond metadata-secret enforcement.
- Duplicating the saved-layout contract doc unless the chosen key policy materially narrows the documented contract.
Avoided Traps
- Treating this as a blocker for PR #13152: the review verdict was approval; this is a follow-up, not a hidden Required Action.
- Adding another doc-only warning while the code still accepts the unsafe metadata shape.
- Persisting a broader runtime/session/window object to solve a metadata problem.
Related
Related: #13152
Related: #13147
Related: #13030
Related: #13012
Origin Session ID: 0ed5b1b0-739e-40e5-93e6-21a2e567ec24
Handoff Retrieval Hint: "dock saved layout metadata apiKey secret createSavedLayout restoreSavedLayout"; review anchor PR #13152 Ada approval PRR_kwDODSospM8AAAABC8D5zQ.
Context
Follow-up from the cross-family review of PR #13152. The PR shipped saved-layout helpers for
Neo.dashboard.DockZoneModeland was approved as merge-eligible; the review surfaced one non-blocking security-contract watch item: the helper rejects known runtime/credential keys via a denylist and rejects non-JSON values, but free-form metadata can still persist a JSON string under a non-denylisted sensitive key such asapiKey,sessionKey, orauthKey.Release classification: post-release (Approve-as-is follow-up — non-blocking; #13152 remains merge-eligible without this).
Live latest-open sweep: checked latest 20 open issues immediately before creation; no equivalent dock saved-layout metadata-secret ticket found. A2A in-flight claim sweep: checked latest 30 messages; no overlapping
[lane-claim]/[lane-intent]found. Local exact sweep:rg "dock layout|dock-layout|saved layout|saved-layout|dockLayout|metadata secret|apiKey|sessionKey|authKey|HarnessDockZoneModel|createSavedLayout|restoreSavedLayout" resources/content/issues resources/content/discussions; no equivalent ticket found. KB semantic sweep:ask_knowledge_base("dock layout saved layout metadata secrets createSavedLayout restoreSavedLayout HarnessDockZoneModel")confirmedHarnessDockZoneModel.mdalready documents that layout metadata must not contain secrets.The Problem
The documented contract is stricter than the current mechanical guard.
learn/agentos/HarnessDockZoneModel.mdalready states that saved-layout metadata is JSON-only descriptive data and must not contain credentials, PATs, access tokens, or harness bridge tokens. PR #13152 enforces part of that boundary by rejecting non-JSON values and known forbidden key names, but the free-formmetadataobject remains denylist-governed.That means a value like
{metadata: {apiKey: "sk-live-x"}}is JSON-serializable and can round-trip unless the key appears in the exact forbidden-key set. The issue is not that #13152 is unsafe to merge; metadata is operator-authored annotation, and the PR already blocks the obvious runtime/preview credential keys. The issue is that the persistence helper now represents a durable contract, and future storage/UI consumers will treat its output as safe unless the contract is enforced mechanically or narrowed explicitly.The Architectural Reality
Neo.dashboard.DockZoneModelowns the pure saved-layout projection:DockZoneModel.LAYOUT_SCHEMAdefinesneo.harness.dockLayout.v1.createSavedLayout(document, metadata)wraps a validated dock-zone document into a saved-layout object.restoreSavedLayout(savedLayout)fail-closes on malformed wrappers before returning a dock-zone document.learn/agentos/HarnessDockZoneModel.mdis the durable contract doc fordockZone.v1,dockPreview.v1, anddockLayout.v1.The current contract doc already covers the no-secret metadata rule, so this ticket should not add a redundant doc-only restatement. The work belongs in the helper/test layer unless implementation discovers the doc wording needs a narrow sync.
The Fix
Choose one narrow contract shape and enforce it in
src/dashboard/DockZoneModel.mjswith focused tests intest/playwright/unit/dashboard/DockZoneModel.spec.mjs:apiKey,sessionKey,authKey, and compound forms likeapiToken, while preserving the current recursive JSON-only guard; ormetadatato documented non-secret operator annotations and fail closed on suspicious key names.The implementation should keep the helper pure and storage-backend-free. It should not introduce a persistence service or UI in this ticket.
Contract Ledger Matrix
DockZoneModel.createSavedLayout(document, metadata)metadata handlinglearn/agentos/HarnessDockZoneModel.mdsaved-layout metadata contract; PR #13152 review finding{layout: null, errors: [...]}without mutating caller input; preserve existing non-secret metadata behaviorHarnessDockZoneModel.mdcontract already states no-secret metadata; update only if exact behavior narrows the documented contractDockZoneModel.restoreSavedLayout(savedLayout)metadata handling{document: null, errors: [...]}; never normalize/restore the unsafe layoutDecision Record impact
none — this tightens the implementation to match the existing dock-zone contract; no ADR change.
Acceptance Criteria
createSavedLayout()rejects a wrapper-level metadata object containing secret-like keys not covered by the current exact denylist, including at leastapiKey,sessionKey, andauthKeyor a justified alternative key policy.restoreSavedLayout()rejects a saved-layout wrapper containing the same secret-like metadata keys.Out of Scope
Avoided Traps
Related
Related: #13152 Related: #13147 Related: #13030 Related: #13012
Origin Session ID: 0ed5b1b0-739e-40e5-93e6-21a2e567ec24
Handoff Retrieval Hint: "dock saved layout metadata apiKey secret createSavedLayout restoreSavedLayout"; review anchor PR #13152 Ada approval
PRR_kwDODSospM8AAAABC8D5zQ.