dodo-mcp 1.2.1 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +37 -6
  2. package/dist/cli/dataRecovery.js +38 -0
  3. package/dist/cli/dataRecovery.js.map +1 -0
  4. package/dist/cli/deployment.js +61 -0
  5. package/dist/cli/deployment.js.map +1 -0
  6. package/dist/cli/main.js +36 -1
  7. package/dist/cli/main.js.map +1 -1
  8. package/dist/config/globalConfig.js +3 -5
  9. package/dist/config/globalConfig.js.map +1 -1
  10. package/dist/errors.js +1 -1
  11. package/dist/errors.js.map +1 -1
  12. package/dist/ipc/server.js +2 -0
  13. package/dist/ipc/server.js.map +1 -1
  14. package/dist/ipc/timeouts.js +5 -0
  15. package/dist/ipc/timeouts.js.map +1 -0
  16. package/dist/platform/privateFs.js +32 -3
  17. package/dist/platform/privateFs.js.map +1 -1
  18. package/dist/platform/windowsPrivateAcl.js +90 -0
  19. package/dist/platform/windowsPrivateAcl.js.map +1 -0
  20. package/dist/security/outboundAddress.js +20 -0
  21. package/dist/security/outboundAddress.js.map +1 -0
  22. package/dist/server/aiAdmin.js +21 -24
  23. package/dist/server/aiAdmin.js.map +1 -1
  24. package/dist/server/appServer.js +3 -6
  25. package/dist/server/appServer.js.map +1 -1
  26. package/dist/server/bootstrap.js +14 -0
  27. package/dist/server/bootstrap.js.map +1 -1
  28. package/dist/server/configSession.js +21 -0
  29. package/dist/server/configSession.js.map +1 -0
  30. package/dist/server/configUi/index.html +3 -0
  31. package/dist/server/configUi/ui/dataRecovery.js +90 -0
  32. package/dist/server/configUi/ui/deployment.js +111 -0
  33. package/dist/server/configUi/ui/recovery.js +167 -0
  34. package/dist/server/configUi/workbench.js +23 -7
  35. package/dist/server/installationRuntime.js +3 -1
  36. package/dist/server/installationRuntime.js.map +1 -1
  37. package/dist/server/instructions.js.map +1 -1
  38. package/dist/server/ipcDispatch.js +219 -0
  39. package/dist/server/ipcDispatch.js.map +1 -1
  40. package/dist/server/localConfig.js +34 -11
  41. package/dist/server/localConfig.js.map +1 -1
  42. package/dist/server/remoteConfig.js +23 -13
  43. package/dist/server/remoteConfig.js.map +1 -1
  44. package/dist/server/stdioServer.js +2 -5
  45. package/dist/server/stdioServer.js.map +1 -1
  46. package/dist/services/ai/network.js +1 -18
  47. package/dist/services/ai/network.js.map +1 -1
  48. package/dist/services/ai/subagents.js +5 -2
  49. package/dist/services/ai/subagents.js.map +1 -1
  50. package/dist/services/assistance/verification.js +43 -10
  51. package/dist/services/assistance/verification.js.map +1 -1
  52. package/dist/services/changes/applier.js +472 -297
  53. package/dist/services/changes/applier.js.map +1 -1
  54. package/dist/services/git/gitService.js +150 -34
  55. package/dist/services/git/gitService.js.map +1 -1
  56. package/dist/services/jobs/jobManager.js +59 -2
  57. package/dist/services/jobs/jobManager.js.map +1 -1
  58. package/dist/services/jobs/spool.js +9 -3
  59. package/dist/services/jobs/spool.js.map +1 -1
  60. package/dist/services/multimodal/mediaService.js +6 -6
  61. package/dist/services/multimodal/mediaService.js.map +1 -1
  62. package/dist/services/recovery/configKeys.js +115 -0
  63. package/dist/services/recovery/configKeys.js.map +1 -0
  64. package/dist/services/recovery/configVault.js +361 -0
  65. package/dist/services/recovery/configVault.js.map +1 -0
  66. package/dist/services/recovery/contracts.js +24 -0
  67. package/dist/services/recovery/contracts.js.map +1 -0
  68. package/dist/services/recovery/databaseAwareness.js +179 -0
  69. package/dist/services/recovery/databaseAwareness.js.map +1 -0
  70. package/dist/services/recovery/deploymentArchive.js +129 -0
  71. package/dist/services/recovery/deploymentArchive.js.map +1 -0
  72. package/dist/services/recovery/deploymentContracts.js +70 -0
  73. package/dist/services/recovery/deploymentContracts.js.map +1 -0
  74. package/dist/services/recovery/deploymentHealth.js +143 -0
  75. package/dist/services/recovery/deploymentHealth.js.map +1 -0
  76. package/dist/services/recovery/deploymentMaintenance.js +197 -0
  77. package/dist/services/recovery/deploymentMaintenance.js.map +1 -0
  78. package/dist/services/recovery/deploymentService.js +429 -0
  79. package/dist/services/recovery/deploymentService.js.map +1 -0
  80. package/dist/services/recovery/deploymentSource.js +36 -0
  81. package/dist/services/recovery/deploymentSource.js.map +1 -0
  82. package/dist/services/recovery/dockerDeployment.js +172 -0
  83. package/dist/services/recovery/dockerDeployment.js.map +1 -0
  84. package/dist/services/recovery/drift.js +178 -0
  85. package/dist/services/recovery/drift.js.map +1 -0
  86. package/dist/services/recovery/evidence.js +127 -0
  87. package/dist/services/recovery/evidence.js.map +1 -0
  88. package/dist/services/recovery/gitCopies.js +219 -0
  89. package/dist/services/recovery/gitCopies.js.map +1 -0
  90. package/dist/services/recovery/history.js +423 -0
  91. package/dist/services/recovery/history.js.map +1 -0
  92. package/dist/services/recovery/recoveryService.js +521 -0
  93. package/dist/services/recovery/recoveryService.js.map +1 -0
  94. package/dist/services/recovery/storage.js +344 -0
  95. package/dist/services/recovery/storage.js.map +1 -0
  96. package/dist/services/schedules/scheduleService.js +9 -10
  97. package/dist/services/schedules/scheduleService.js.map +1 -1
  98. package/dist/store/db.js +71 -0
  99. package/dist/store/db.js.map +1 -1
  100. package/dist/tools/catalog.js +4 -0
  101. package/dist/tools/catalog.js.map +1 -1
  102. package/dist/tools/changeTools.js +1 -1
  103. package/dist/tools/changeTools.js.map +1 -1
  104. package/dist/tools/context.js +23 -3
  105. package/dist/tools/context.js.map +1 -1
  106. package/dist/tools/deploymentTools.js +58 -0
  107. package/dist/tools/deploymentTools.js.map +1 -0
  108. package/dist/tools/directTools.js +9 -5
  109. package/dist/tools/directTools.js.map +1 -1
  110. package/dist/tools/gitTools.js +7 -2
  111. package/dist/tools/gitTools.js.map +1 -1
  112. package/dist/tools/jobTools.js +4 -4
  113. package/dist/tools/jobTools.js.map +1 -1
  114. package/dist/tools/projectTools.js +1 -0
  115. package/dist/tools/projectTools.js.map +1 -1
  116. package/dist/tools/recoveryTools.js +79 -0
  117. package/dist/tools/recoveryTools.js.map +1 -0
  118. package/dist/tools/runtimeTools.js +1 -1
  119. package/dist/tools/runtimeTools.js.map +1 -1
  120. package/dist/tools/surface.js +6 -1
  121. package/dist/tools/surface.js.map +1 -1
  122. package/dist/tunnel/credentials.js +1 -1
  123. package/dist/tunnel/credentials.js.map +1 -1
  124. package/docs/ARCHITECTURE.md +92 -2
  125. package/docs/CI.md +118 -0
  126. package/docs/COMPATIBILITY.md +7 -6
  127. package/docs/EVALUATION.md +17 -12
  128. package/docs/MANUAL_ACCEPTANCE.md +72 -2
  129. package/docs/MCP_CONNECTIONS.md +238 -0
  130. package/docs/RECOVERY.md +448 -0
  131. package/docs/RELEASE_1.3.0.md +64 -0
  132. package/docs/RELEASE_1.3.1.md +49 -0
  133. package/docs/RELEASE_NOTES.md +31 -0
  134. package/docs/SECURITY.md +123 -0
  135. package/docs/TEST_REPORT.md +312 -5
  136. package/docs/TUNNEL.md +6 -0
  137. package/docs/WEB_CLIENTS.md +3 -0
  138. package/docs/adr/047-temporary-remote-config.md +11 -0
  139. package/docs/adr/051-source-recovery-foundation.md +45 -0
  140. package/docs/adr/052-reviewed-source-restore.md +50 -0
  141. package/docs/adr/053-content-drift-and-private-git-recovery.md +29 -0
  142. package/docs/adr/054-verified-source-checkpoints.md +58 -0
  143. package/docs/adr/055-reviewed-docker-deployments.md +42 -0
  144. package/docs/adr/056-owner-data-recovery.md +47 -0
  145. package/docs/adr/README.md +7 -0
  146. package/native/private-state-windows.cs +105 -0
  147. package/package.json +8 -1
  148. package/schemas/global-config.schema.json +34 -0
  149. package/schemas/tools.compact.json +175 -15
  150. package/schemas/tools.hybrid.json +350 -16
  151. package/schemas/tools.json +4883 -816
@@ -0,0 +1,29 @@
1
+ # ADR 053: content drift and independent Git recovery copies
2
+
3
+ Status: implemented in the unreleased Recovery candidate (R03); platform evidence is recorded separately.
4
+
5
+ The expected source state is a durable, per-workspace manifest plus committed journal updates. It is **not** the most recently captured checkpoint. Restart, owner checkpoint creation, periodic observations and emergency snapshots never silently adopt external bytes. Only a successfully verified journal transaction or an authenticated owner acknowledgement advances this state. Baseline snapshots remain retention-protected.
6
+
7
+ Mutation targets are checked by content/type/mode/identity, irrespective of mtime or a freshly supplied expectedHash. Non-target changes do not block an unrelated simple edit. Before commands, Git commit hooks and verification recipes, a source scan blocks significant drift. Default significance is at least 20 changed source files **and more than 20%** of baseline file count, or at least 10 deleted source files. These are owner policy thresholds, not a malware classifier. Existing ACL, trust, approvals, sandbox and secret/path restrictions remain authoritative.
8
+
9
+ A 60-second timer skips busy workspaces. Scans have a 15-second default wall budget (owner range 100–60000 ms), entry/file budgets, guarded reads and two content inventories. Watchers are not an authority or prerequisite. Missed events and equal size/mtime edits remain detectable; edits changed back between scans cannot be proven. Interrupted or over-budget scans never acknowledge the state. Jobs retain the project mutation ticket through their bounded post-exit observation. Shell results are attributed as **unknown**, because external programs may write concurrently.
10
+
11
+ Emergency observations preserve the newly observed bytes separately from the original baseline. They are not known-good or verification results, and cannot recover pre-overwrite bytes that were never captured. Ordinary ignored files become tracked after a named journaled mutation; generated/data/secret exclusions still apply. Reviewed R02 restore plans have a private exemption from the drift-baseline comparison only; immutable plan/hash, caller, epoch, source scope, new before-image capture, expected hashes and journal verification still apply. This lets an owner restore externally changed files without first accepting them as normal work.
12
+
13
+ ## Git storage decision
14
+
15
+ Git checkpoint refs live in **independent private bare repositories**, not in the working repository. Each source checkpoint at activation/session boundary, before exec, explicit owner checkpoint and successful fresh verification creates a parentless commit and create-only `refs/dodo/snapshots/<snapshot-id>`. Target-only file edits and emergency observations do not run Git. Verification-triggered copies are deduplicated by the owned verification receipt and must still match the verifier at publication; this is not a deployment approval or known-good promotion.
16
+
17
+ The builder uses captured, hash-verified CAS bytes, an empty private index and plumbing commands. It never copies the working object database/history, user index, remotes, alternates, hooks, filters or configuration. Sanitized HEAD/branch/index fingerprint is evidence only. No checkout, reset, add, remote operation or credential helper runs. Missing Git/non-Git projects retain mandatory content backup; optional Git reports its limitation. Git-required projects fail closed when a required full copy cannot be created.
18
+
19
+ Unborn and detached HEAD, parent repositories and worktree gitfiles use source-root scope. Submodule files already materialized in that scope are ordinary captured source; submodule Git history is not copied. LFS pointer bytes remain pointers; no LFS network fetch occurs. Git alone preserves executable bits, not all POSIX modes or empty directories; the separately stored source manifest remains the complete recovery contract.
20
+
21
+ An owner can choose an existing, canonical, private, dedicated empty directory outside registered projects, DODO state and recognized credential namespaces. Subsequent contents must be registered recovery copies. Its filesystem identity is pinned. Missing/replaced volumes fail closed with **no fallback**, even if optional Git was otherwise selected. This is a bare storage backend, not a mirror of all repository history. Default copies use private installation recovery storage.
22
+
23
+ Copy reservations and physical bytes count against the existing project/installation quotas. Contents/refs are checked before READY, then files and directories are synced; source-retention pruning also removes matching owned Git copies. Crash-interrupted copies remain non-READY and counted until safe retention cleanup. They are never automatically executed or treated as valid.
24
+
25
+ ## Owner review and limits
26
+
27
+ Web/CLI scan results are bounded and paginated. Acknowledgement requires the exact observation digest, workspace/epoch, confirmation and live owner authority; it rechecks the content again under the mutation queue. MCP can inspect via `restore_status(scan:true)` but cannot acknowledge/configure. Full/Compact/Hybrid tool counts do not increase in R03.
28
+
29
+ Deleting source files and `.git` inside the original registered root does not remove independent copies. The source restore restores approved files only and does not recreate Git metadata. Replacing/deleting the root directory itself still fails the root-identity contract: recover to a new directory manually from the independent copy, review it, then register that directory. Storage on the same device/account is not protection from device failure or a compromised owner account. Database/volume/destructive command side effects remain outside source recovery.
@@ -0,0 +1,58 @@
1
+ # ADR 054: Source verification evidence and owner checkpoint labels
2
+
3
+ Status: implemented in the unreleased Recovery candidate; deployment/database recovery are separate work.
4
+
5
+ `verify_changes` remains the only verification executor. Its existing scope,
6
+ trust, approval, sandbox, job budget and idempotency gates remain authoritative.
7
+ When Recovery is enabled for a registered project it captures a complete source
8
+ checkpoint BEFORE launching the selected recipes, binds its manifest digest to
9
+ the private verification record, and rechecks source before launch and at report.
10
+ The independent before-exec copies are retained as well.
11
+
12
+ The binding references workspace, caller and immutable snapshot. Evidence records
13
+ contain selected recipe digests, actual job IDs/exits and recognized count scalars,
14
+ parser version, timestamps and an allowlisted Node/platform/epoch description.
15
+ They contain neither log contents nor raw environment variables. Jobs and their
16
+ output are read through existing services; no endpoint accepts a caller-supplied
17
+ pass result. The selected checks are the required set for that run, not every
18
+ possible repository check. Missing recommended checks remain explicitly listed.
19
+
20
+ Recovery compares its full included-source manifest (including previously tracked
21
+ ignored targets) as well as the narrower assistance snapshot. Recipe, runtime,
22
+ parser or observed source drift invalidates freshness, including an observed edit
23
+ subsequently reverted. Truncated output, unknown/zero test counts, inconsistent
24
+ counts and skipped tests cannot establish VERIFIED. Build/lint command success is
25
+ only evidence for that selected command, not proof of unrun tests. Generated source
26
+ requires a new snapshot and verification. Changes reverted BETWEEN observations,
27
+ unmeasured dependencies/environment and deceptive repository test scripts remain
28
+ outside the guarantee. This is not an OS sandbox or proof of program correctness.
29
+
30
+ SAVED means independent bytes passed integrity checks at capture. VERIFIED,
31
+ FAILED, INCONCLUSIVE and STALE describe bounded verification evidence. Lists are
32
+ historical observations marked refresh-required; explicit inspection rechecks
33
+ current source, recipes and jobs. Overview exposes only a historical timestamp,
34
+ never a cross-caller verification ID. Recovery metadata does not display logs or
35
+ private filesystem locations.
36
+
37
+ Owner checkpoint names are a separate revisioned table. Updates use an exact
38
+ expected revision under the project mutation queue and a SQLite transaction,
39
+ rechecking owner authority after asynchronous integrity reads. Removing a name
40
+ leaves a revision tombstone, preventing ABA overwrites. A durable event records
41
+ both old and new targets with the audit entry. Names never alter manifest bytes,
42
+ certify tests, grant permission or set PRODUCTION_KNOWN_GOOD. Production names are
43
+ reserved; production evidence is NOT_CONFIGURED and database recovery NOT_SUPPORTED.
44
+
45
+ Named targets and explicit pins retain their snapshots and associated session
46
+ references. Retention preview and automatic pruning share one selection function;
47
+ preview has no delete side effects. Byte estimates are logical, not a promise of
48
+ immediate physical reclamation because objects can be shared and have a GC grace
49
+ period. Reviewed plans, active/unresolved history and the latest baselines remain
50
+ protected. No public purge/mark/pin tools are introduced.
51
+
52
+ The owner web panel lives in a separate bundled module. It uses authenticated
53
+ fetch, current project/epoch and the existing confirmation UI/CSP. Text-only DOM
54
+ construction escapes names, paths, errors and diffs. Durable journal receipts are
55
+ queried after reconnect; an uncertain restore is never repeated with a new key.
56
+ Full catalog stays 148 and Compact 20; existing Recovery operations expose only
57
+ caller-owned records under current ACLs. Private owner web and CLI share IPC
58
+ validation for evidence, names, pins and retention preview.
@@ -0,0 +1,42 @@
1
+ # ADR 055 — Reviewed Docker deployments and image recovery
2
+
3
+ Status: accepted in unreleased working source; native platform release gates pending.
4
+
5
+ A source checkpoint does not prove which image is running or that a database can
6
+ be undone. Deployment is an opt-in owner-registered adapter, separate from
7
+ source recovery, which remains enabled by default.
8
+
9
+ The first adapter controls one explicitly named Docker Compose service/context.
10
+ It builds a bounded tar stream from verified, policy-filtered CAS bytes and uses
11
+ an immutable image ID. It never consumes repository Compose hooks. Source labels
12
+ are metadata, not proof: declared source mappings are compared against actual
13
+ image/container bytes, with archive traversal/link/mount exclusions. A stopped,
14
+ network-disabled probe records its identity before creation and is never started.
15
+
16
+ Every effect uses the shared invocation authority, project mutation queue,
17
+ JobManager, sandbox and exec policy. A plan binds workspace/epoch, target revision,
18
+ source manifest and required test evidence. Health checks have separately declared
19
+ network permission, DNS pinning, redirect rejection and private/admin endpoint
20
+ restrictions. Stabilization and a final container identity check precede a
21
+ revisioned known-good pointer update. Image rollback uses a new reviewed plan,
22
+ exact old image and fresh health; it never runs database migrations.
23
+
24
+ Durable state precedes external effects. Restart converts in-flight records to
25
+ UNKNOWN. Retries inspect the receipt rather than repeat commands. Owner-only
26
+ maintenance previews re-observe exact state before acknowledgement or cleanup;
27
+ acknowledging UNKNOWN is not a success/health assertion. The original unknown
28
+ receipt remains available. Private owner review IDs/hashes and expiry protect
29
+ maintenance; these controls are not MCP tools.
30
+
31
+ Retention groups immutable IDs, protects current/previous known-good, active
32
+ containers, unexpired plans, unresolved outcomes, owner pins and other target
33
+ references. Cleanup is explicit, uses exact managed tags/IDs without force, and
34
+ never prunes volumes. Interrupted cleanup is resolved by observation before a new
35
+ review. Source manifests stay protected by deployment provenance independently
36
+ of image retention. Each target admits at most 500 records; owner maintenance
37
+ reviews are bounded at 2,000 per project.
38
+
39
+ Limits: Docker access can affect resources beyond the workspace. Generic shell,
40
+ external CI and owner terminal commands remain outside this adapter's deployment
41
+ guards. Missing/compiled source is not reconstructed, unavailable manifests are
42
+ not fabricated, and source recovery does not include database or secret data.
@@ -0,0 +1,47 @@
1
+ # ADR 056 — Owner-controlled database awareness and private config recovery
2
+
3
+ Status: implemented in unreleased working source; platform/release gates remain independent.
4
+
5
+ Source snapshots cannot reverse database transactions or safely include secrets.
6
+ We keep both data classes outside source CAS/Git and outside MCP restore authority.
7
+
8
+ `RecoveryDatabases` is a bounded read-only SQLite migration-table adapter. Owner
9
+ registration pins the canonical file identity and policy. Explicit compatibility
10
+ rules bind manifest hash and target revision. Only ordinary tables, bounded string
11
+ IDs and strict identifiers are accepted; read connections use query-only and
12
+ trusted-schema-off. No SQL, remote DB, mutation or generic rollback API exists.
13
+ Source preview/apply checks the rule and rechecks after the pre-restore checkpoint;
14
+ post-apply drift is reported without auto-undo. This is not a cross-process database
15
+ lock or a proof of semantic compatibility. Missing information is UNKNOWN.
16
+
17
+ `RecoveryConfigVault` registers existing private, source-denied project files.
18
+ AES-256-GCM uses a fresh nonce and associated data binding version, project/root,
19
+ backup, target/revision and key reference. Ciphertext goes into dedicated private
20
+ SQLite tables; a random 256-bit key stays in the OS credential store. `OSRecoveryKeys`
21
+ uses fixed helper protocols with stdin for secret input, bounded output, no shell,
22
+ minimal environment and no plaintext fallback. Owner UI/CLI see redacted metadata,
23
+ not decrypted bytes. No new public MCP tools, credential-export endpoint or
24
+ permission bypass is introduced.
25
+
26
+ Owner actions use shared per-project mutation queues, live owner leases and context.
27
+ Restore previews bind expiry, epoch, target revision, authenticated ciphertext,
28
+ original file identity and a keyed content digest. An encrypted pre-restore backup
29
+ and UNKNOWN receipt commit before any write. A single existing file is rewritten in
30
+ place to preserve its private metadata and avoid temporary plaintext copies. This
31
+ is explicitly not crash-atomic; read-back certifies only completed writes. UNKNOWN
32
+ never auto-replays; a separate owner review may restore the encrypted before-copy.
33
+ The owner must stop/restart dependent services. Same-user external writers are not
34
+ confined by DODO's mutation queue.
35
+
36
+ Keys rotate by creating a new OS reference; old backups retain old keys. Key loss
37
+ is unrecoverable through DODO. Retention is separate from source, bounded, and
38
+ protects restore-referenced copies. Full metadata/receipt history is intentionally
39
+ bounded instead of evicting uncertain recovery evidence. SQLite row deletion is
40
+ logical retention, not guaranteed physical secure erasure of storage pages.
41
+
42
+ Tests use real SQLite/files and authenticated private HTTP for permissions,
43
+ compatibility drift, ciphertext tampering, missing/wrong keys, rotation, stale
44
+ context, file conflicts and crash receipts. Protocol fixtures separately exercise
45
+ all three OS-helper boundaries; actual credential-store tests opt in with
46
+ `DODO_TEST_OS_RECOVERY_KEYS=1` and create/delete only a random fixture item.
47
+ Fixtures do not substitute for live manual production recovery.
@@ -66,3 +66,10 @@ decisions; ADR-011+ resolve implementation ambiguities without reducing security
66
66
  - [048 — Persistent exclusive Local/Tunnel connection mode](048-persistent-connection-mode.md)
67
67
  - [049 — Owner-approved Android control through ADB](049-android-adb-control.md)
68
68
  - [050 — Cloudflare Local mode and visible per-project access](050-cloudflare-local-and-project-access.md)
69
+
70
+ - [051 — Source Recovery foundation](051-source-recovery-foundation.md)
71
+ - [052 — Reviewed source restore](052-reviewed-source-restore.md)
72
+ - [053 — Content drift and private Git recovery](053-content-drift-and-private-git-recovery.md)
73
+ - [054 — Verified source checkpoints](054-verified-source-checkpoints.md)
74
+ - [055 — Reviewed Docker deployments](055-reviewed-docker-deployments.md)
75
+ - [056 — Owner-controlled database awareness and private config recovery](056-owner-data-recovery.md)
@@ -0,0 +1,105 @@
1
+ // Same admission policy as privateFs.ts, using inbox .NET ACL APIs directly.
2
+ // Target and mode are environment DATA. No shell, arbitrary SID, elevation or
3
+ // permission-result cache. Every invocation rereads owner, DACL and attributes.
4
+ using System;
5
+ using System.IO;
6
+ using System.Security.AccessControl;
7
+ using System.Security.Principal;
8
+
9
+ class DodoPrivateState {
10
+ static readonly AccessControlSections Sections = AccessControlSections.Owner | AccessControlSections.Access;
11
+ static string stage = "input";
12
+ static void Require(bool ok) { if (!ok) throw new InvalidOperationException(); }
13
+ static FileSystemSecurity Read(string path, bool directory) {
14
+ return directory ? (FileSystemSecurity)Directory.GetAccessControl(path, Sections) : File.GetAccessControl(path, Sections);
15
+ }
16
+ static void Write(string path, bool directory, FileSystemSecurity acl) {
17
+ if (directory) Directory.SetAccessControl(path, (DirectorySecurity)acl);
18
+ else File.SetAccessControl(path, (FileSecurity)acl);
19
+ }
20
+ static void PrivateDacl(FileSystemSecurity acl, string sid) {
21
+ bool ownerAllowed = false;
22
+ foreach (FileSystemAccessRule rule in acl.GetAccessRules(true, true, typeof(SecurityIdentifier))) {
23
+ if (rule.AccessControlType != AccessControlType.Allow) continue;
24
+ string id = rule.IdentityReference.Value;
25
+ Require(id == sid || id == "S-1-5-18" || id == "S-1-5-32-544");
26
+ if (id == sid) ownerAllowed = true;
27
+ }
28
+ Require(ownerAllowed);
29
+ }
30
+ static bool CanonicalPrivateDirectory(FileSystemSecurity acl, string sid) {
31
+ if (!acl.AreAccessRulesProtected || acl.GetOwner(typeof(SecurityIdentifier)).Value != sid) return false;
32
+ var remaining = new System.Collections.Generic.HashSet<string>(new string[] { sid, "S-1-5-18", "S-1-5-32-544" });
33
+ foreach (FileSystemAccessRule rule in acl.GetAccessRules(true, true, typeof(SecurityIdentifier))) {
34
+ if (rule.AccessControlType != AccessControlType.Allow || rule.IsInherited
35
+ || rule.FileSystemRights != FileSystemRights.FullControl
36
+ || rule.InheritanceFlags != (InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit)
37
+ || rule.PropagationFlags != PropagationFlags.None || !remaining.Remove(rule.IdentityReference.Value)) return false;
38
+ }
39
+ return remaining.Count == 0;
40
+ }
41
+ static FileAttributes Attributes(string path) {
42
+ FileAttributes value = File.GetAttributes(path);
43
+ Require((value & FileAttributes.ReparsePoint) == 0);
44
+ return value;
45
+ }
46
+ static void Verify(string path, string mode) {
47
+ Require(!String.IsNullOrEmpty(path) && Path.IsPathRooted(path));
48
+ stage = "attributes";
49
+ bool directory = (Attributes(path) & FileAttributes.Directory) != 0;
50
+ using (WindowsIdentity current = WindowsIdentity.GetCurrent()) {
51
+ SecurityIdentifier sid = current.User;
52
+ SecurityIdentifier admin = new SecurityIdentifier("S-1-5-32-544");
53
+ WindowsPrincipal principal = new WindowsPrincipal(current);
54
+ stage = "read";
55
+ FileSystemSecurity acl = Read(path, directory);
56
+ string owner = acl.GetOwner(typeof(SecurityIdentifier)).Value;
57
+ bool repairAdminOwner = owner == admin.Value && principal.IsInRole(admin);
58
+ stage = "owner";
59
+ Require(owner == sid.Value || repairAdminOwner);
60
+ if (mode == "protect") {
61
+ Require(directory);
62
+ DirectorySecurity next = new DirectorySecurity();
63
+ next.SetOwner(sid);next.SetAccessRuleProtection(true, false);
64
+ foreach (string id in new string[] { sid.Value, "S-1-5-18", admin.Value })
65
+ next.AddAccessRule(new FileSystemAccessRule(new SecurityIdentifier(id), FileSystemRights.FullControl,
66
+ InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow));
67
+ // Reapplying an identical inheritable DACL propagates security to live
68
+ // descendants while another owner CLI may be protecting those paths.
69
+ // Skip only an EXACT policy match from this fresh read, never a cache.
70
+ if (!CanonicalPrivateDirectory(acl, sid.Value)) {
71
+ stage = "protect-write";
72
+ Write(path, true, next);
73
+ }
74
+ } else if (mode == "verify") {
75
+ stage = "verify-dacl";
76
+ PrivateDacl(acl, sid.Value);
77
+ if (repairAdminOwner) {
78
+ string before = acl.GetSecurityDescriptorSddlForm(AccessControlSections.Access);
79
+ stage = "repair-owner";
80
+ acl.SetOwner(sid);Write(path, directory, acl);
81
+ Require(Read(path, directory).GetSecurityDescriptorSddlForm(AccessControlSections.Access) == before);
82
+ }
83
+ } else throw new InvalidOperationException();
84
+ stage = "final-attributes";
85
+ Require(((Attributes(path) & FileAttributes.Directory) != 0) == directory);
86
+ stage = "final-read";
87
+ FileSystemSecurity final = Read(path, directory);
88
+ stage = "final-owner";
89
+ Require(final.GetOwner(typeof(SecurityIdentifier)).Value == sid.Value);
90
+ stage = "final-dacl";
91
+ PrivateDacl(final, sid.Value);
92
+ }
93
+ }
94
+ static int Main() {
95
+ try {
96
+ Verify(Environment.GetEnvironmentVariable("DODO_PRIVATE_PATH"), Environment.GetEnvironmentVariable("DODO_PRIVATE_MODE"));
97
+ Console.Write("private");return 0;
98
+ } catch (Exception error) {
99
+ // Fixed labels and a numeric OS error only; never emit paths, identities,
100
+ // ACL contents or exception text. Callers still fail closed.
101
+ Console.Error.Write("private ACL verification failed; stage=" + stage + "; hresult=" + error.HResult);
102
+ return 1;
103
+ }
104
+ }
105
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dodo-mcp",
3
- "version": "1.2.1",
3
+ "version": "1.3.1",
4
4
  "author": "Arthittakun",
5
5
  "description": "DODO — local-first MCP server with multi-project coding, AI providers, sub-agents, context, memory, media and workflow tools; embedded OAuth and workspace permissions.",
6
6
  "type": "module",
@@ -26,6 +26,7 @@
26
26
  "docs/ANDROID.md",
27
27
  "docs/AUTH.md",
28
28
  "docs/BRAIN.md",
29
+ "docs/CI.md",
29
30
  "docs/COMPATIBILITY.md",
30
31
  "docs/CONTEXT.md",
31
32
  "docs/CONFIRMATION.md",
@@ -37,6 +38,7 @@
37
38
  "docs/MEMORY.md",
38
39
  "docs/MULTIMODAL.md",
39
40
  "docs/PROJECTS.md",
41
+ "docs/RECOVERY.md",
40
42
  "docs/RESOURCES.md",
41
43
  "docs/RUNTIME.md",
42
44
  "docs/RELEASE_1.0.0.md",
@@ -49,11 +51,14 @@
49
51
  "docs/RELEASE_1.1.0.md",
50
52
  "docs/RELEASE_1.2.0.md",
51
53
  "docs/RELEASE_1.2.1.md",
54
+ "docs/RELEASE_1.3.0.md",
55
+ "docs/RELEASE_1.3.1.md",
52
56
  "docs/RELEASE_NOTES.md",
53
57
  "docs/SCHEDULES.md",
54
58
  "docs/SECURITY.md",
55
59
  "docs/TEST_REPORT.md",
56
60
  "docs/TUNNEL.md",
61
+ "docs/MCP_CONNECTIONS.md",
57
62
  "docs/WEB_CLIENTS.md",
58
63
  "docs/WINDOWS.md",
59
64
  "docs/WINDOWS_SETUP.md",
@@ -80,6 +85,7 @@
80
85
  "test:all": "npm run typecheck && npm run lint && vitest run tests/unit tests/integration tests/security tests/compatibility && npm run test:pack",
81
86
  "release:gate": "node scripts/release-gate.mjs --allow-dirty",
82
87
  "release:gate:ci": "node scripts/release-gate.mjs",
88
+ "ci:windows:focus": "node scripts/windows-focus.mjs",
83
89
  "release:gate:strict": "node scripts/release-gate.mjs --release",
84
90
  "prepublishOnly": "node scripts/guard-publish.mjs",
85
91
  "desktop:setup": "node dist/cli/main.js desktop setup",
@@ -101,6 +107,7 @@
101
107
  "picomatch": "^4.0.7",
102
108
  "playwright": "1.63.0",
103
109
  "sharp": "0.35.4",
110
+ "tar-stream": "3.2.1",
104
111
  "typescript": "5.9.3",
105
112
  "zod": "^4.2.0"
106
113
  },
@@ -7,6 +7,39 @@
7
7
  "type": "number",
8
8
  "const": 1
9
9
  },
10
+ "recovery": {
11
+ "default": {
12
+ "version": 1,
13
+ "storageBytes": 21474836480,
14
+ "freeFloorBytes": 524288000
15
+ },
16
+ "type": "object",
17
+ "properties": {
18
+ "version": {
19
+ "default": 1,
20
+ "type": "number",
21
+ "const": 1
22
+ },
23
+ "storageBytes": {
24
+ "default": 21474836480,
25
+ "type": "integer",
26
+ "minimum": 1048576,
27
+ "maximum": 1099511627776
28
+ },
29
+ "freeFloorBytes": {
30
+ "default": 524288000,
31
+ "type": "integer",
32
+ "minimum": 0,
33
+ "maximum": 107374182400
34
+ }
35
+ },
36
+ "required": [
37
+ "version",
38
+ "storageBytes",
39
+ "freeFloorBytes"
40
+ ],
41
+ "additionalProperties": false
42
+ },
10
43
  "publicUrl": {
11
44
  "type": "string",
12
45
  "format": "uri"
@@ -478,6 +511,7 @@
478
511
  },
479
512
  "required": [
480
513
  "version",
514
+ "recovery",
481
515
  "port",
482
516
  "configPort",
483
517
  "allowedHosts",