@cairnkeep/cli 2.22.3 → 2.22.5

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 (66) hide show
  1. package/CHANGELOG.md +79 -1
  2. package/containers/healthcheck.mjs +27 -33
  3. package/containers/quadlet/cairnkeep.container +1 -1
  4. package/docs/containers.md +12 -0
  5. package/docs/domain-knowledge.md +15 -0
  6. package/docs/learning/CURRICULUM-MAP.md +1 -1
  7. package/docs/learning/FEATURE-GUIDE.md +1 -1
  8. package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
  9. package/docs/learning/lessons/L01-safe-trial.md +3 -3
  10. package/docs/learning/lessons/L02-installation.md +2 -2
  11. package/docs/learning/lessons/L03-first-project.md +1 -1
  12. package/docs/learning/lessons/L19-validated-skill-improvement.md +1 -1
  13. package/docs/learning/lessons/L20-mcp-tool-profiles.md +1 -1
  14. package/docs/learning/lessons/L21-context-packs.md +1 -1
  15. package/docs/learning/lessons/L22-native-windows.md +2 -2
  16. package/docs/learning/lessons/L23-guided-setup.md +1 -1
  17. package/docs/learning/lessons/L24-okf-exchange.md +1 -1
  18. package/docs/learning/lessons/L25-playbooks.md +1 -1
  19. package/docs/learning/lessons/L26-context-intelligence.md +1 -1
  20. package/docs/learning/lessons/L27-ecosystem-evidence.md +1 -1
  21. package/docs/learning/lessons/L28-security-assurance.md +72 -2
  22. package/docs/learning/tracks/operator.md +6 -1
  23. package/docs/learning/video-scripts/V28-security-assurance.md +65 -0
  24. package/docs/native-windows.md +10 -0
  25. package/docs/operating.md +10 -0
  26. package/docs/privacy-and-data-flow.md +6 -0
  27. package/docs/releasing.md +61 -2
  28. package/docs/security-assurance.md +82 -0
  29. package/examples/eval/bundled-fake.json +1 -1
  30. package/mcp-memory-server/dist/capability-registry.js +2 -15
  31. package/mcp-memory-server/dist/context-pack-retrieval.js +6 -6
  32. package/mcp-memory-server/dist/domain-retrieval-provider.js +100 -45
  33. package/mcp-memory-server/dist/eval-plan.js +15 -22
  34. package/mcp-memory-server/dist/eval-runner.js +17 -10
  35. package/mcp-memory-server/dist/eval-workspace.js +82 -23
  36. package/mcp-memory-server/dist/failure-signature.js +29 -8
  37. package/mcp-memory-server/dist/graph-cli.js +25 -10
  38. package/mcp-memory-server/dist/note-store.js +24 -4
  39. package/mcp-memory-server/dist/okf.js +4 -9
  40. package/mcp-memory-server/dist/playbook-receipt.js +3 -17
  41. package/mcp-memory-server/dist/skill-store.js +10 -26
  42. package/mcp-memory-server/dist/stable-file.js +85 -0
  43. package/mcp-memory-server/dist/work-evidence-store.js +7 -5
  44. package/omp/extensions/cairnkeep-capture.ts +29 -17
  45. package/package.json +1 -1
  46. package/scripts/lib/opencode-v2-plugin-harness.mjs +20 -3
  47. package/scripts/lib/release-verification.mjs +131 -0
  48. package/scripts/lib/stable-file.mjs +75 -0
  49. package/scripts/playbook-instructions.mjs +42 -20
  50. package/scripts/setup-core.mjs +5 -36
  51. package/scripts/setup-reconcile.mjs +32 -32
  52. package/scripts/setup.mjs +7 -31
  53. package/scripts/test-ci-sharding.sh +5 -0
  54. package/scripts/test-container-health.mjs +56 -0
  55. package/scripts/test-container.sh +8 -1
  56. package/scripts/test-learning-docs.sh +8 -0
  57. package/scripts/test-omp-staged-security.mjs +110 -0
  58. package/scripts/test-release-verification.mjs +109 -0
  59. package/scripts/test-release-verification.sh +4 -0
  60. package/scripts/test-runtime-file-security.mjs +311 -0
  61. package/scripts/test-setup-reconcile.mjs +4 -2
  62. package/scripts/test-windows-native.mjs +4 -0
  63. package/scripts/verify-omp-capture.mjs +1 -0
  64. package/scripts/verify-phase19-runtime-evidence.mjs +43 -3
  65. package/scripts/verify-published-release.mjs +138 -0
  66. package/scripts/windows-platform.mjs +54 -22
package/CHANGELOG.md CHANGED
@@ -5,6 +5,83 @@ All notable user-facing changes are documented here. This project follows
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [2.22.5] - 2026-10-08
9
+
10
+ ### Operations
11
+
12
+ - Add an explicit post-publication verifier for exact-tree CI candidates,
13
+ release and registry bytes, npm signatures/attestations, anonymous
14
+ dual-architecture OCI images, bound SBOMs and exact-source signed provenance.
15
+ Refuse partial verification and report replacement; keep artifact evidence
16
+ separate from deployment readiness. Add offline positive/mutation controls.
17
+
18
+ ### Security
19
+
20
+ - Bind note-transaction pre-image hashes to bounded inspected descriptors;
21
+ reject linked, replaced, growing or over-64-MiB files before accepting their
22
+ hashes. Create developer note fixtures exclusively without overwriting a
23
+ competing file. Add actual runtime replacement and fixture race controls.
24
+ - Use bounded descriptor reads for developer benchmark/import/note snapshots,
25
+ capability leases, corrupt-database controls and runtime-evidence files.
26
+ Preserve existing corruption, permissions and exact-byte assertions; keep
27
+ remote-context test labels static so provider values cannot forge log lines.
28
+ These controls do not make note publication a directory-wide transaction or
29
+ certify that every historical security-analysis finding is resolved.
30
+
31
+ ## [2.22.4] - 2026-10-07
32
+
33
+ ### Security
34
+
35
+ - Remove ambiguous nested path expressions from failure signatures. Crafted
36
+ slash-heavy messages no longer cause exponential backtracking; existing v1
37
+ fixture signatures remain byte-equivalent. Add timeout-isolated regression
38
+ inputs and a reproducible generated corpus.
39
+ - Bind capability compatibility configuration, skill artifacts/targets/backups
40
+ and work-evidence reads to inspected descriptors with fixed byte bounds,
41
+ identity rechecks and strict UTF-8. Reject replacement, growth and special-file
42
+ substitution; preserve private-ledger permissions and ordinary project-file
43
+ modes. Keep schemas, feature defaults and explicit approval gates unchanged.
44
+ Update security guidance and the learning/video supplement; historical
45
+ analysis findings remain subject to individual triage.
46
+ - Bind evaluation inputs/note snapshots, OKF import/export, graph artifacts,
47
+ playbook receipts and progressive-context cache reads to bounded inspected
48
+ descriptors. Reject replacement and growth before accepting content; publish
49
+ snapshots from the validated bytes instead of reopening source paths.
50
+ Rebuild unsafe derived caches deterministically without changing pack
51
+ enablement or skill approval. Add public-reader regression controls and
52
+ update security, learning and video guidance.
53
+ - Bound setup policy/state/ownership and instruction-file reads to 1 MiB;
54
+ bound OMP staged candidates to 8 KiB and exclude linked or oversized files.
55
+ Recheck observed setup/instruction targets before publication; preserve
56
+ caller edits detected after inspection, original instruction modes and
57
+ failed-operation state. Keep private approvals and machine sync explicit.
58
+ On Windows, use content ownership rather than POSIX executable-mode bits
59
+ when reconciling generated files; keep private-state ACL enforcement.
60
+ - Publish evaluation overlays and Windows managed files through exclusive
61
+ descriptor-written temporary files. Reject substituted targets, clean only
62
+ owned temporary files, and never unlink live Windows data to retry a denied
63
+ replacement. Bound compressed and expanded memory imports to 512 MiB.
64
+ - Keep container health checks on a validated numeric loopback port, using the
65
+ server's bounded credential resolver. Refuse provider URL credentials and
66
+ redirects, bound AnythingLLM query responses to 8 MiB, and sanitize provider
67
+ HTTP, network and stream failures without including response bodies.
68
+ Reject workspace dot segments, including shorthand OpenViking traversal.
69
+ Preserve default retrieval providers and successful response shapes.
70
+
71
+ ### CI
72
+
73
+ - Preserve the bundled evaluation task set's canonical LF bytes in native
74
+ Windows source checkouts. Add a real Git CRLF-conversion regression control;
75
+ keep the installed-package exact-byte integrity check unchanged.
76
+ - Canonicalize bounded-read fixture paths before injecting file changes, covering
77
+ Windows short temporary-directory names and a real POSIX alias control.
78
+ - Run focused bounded-input controls before the longer Windows smoke shards on
79
+ all supported Node versions; retain every complete-catalog and install gate.
80
+ - Wait for completed OpenCode V2 fixture capture before reading its database,
81
+ under a bounded event-stream completion deadline. A deterministic delayed
82
+ completion control rejects the old file-existence readiness assumption;
83
+ production capture behavior is unchanged.
84
+
8
85
  ## [2.22.3] - 2026-10-07
9
86
 
10
87
  ### Fixed
@@ -727,7 +804,8 @@ stored memories.
727
804
  - Preserve executable permissions and Bash 3.2 portability on macOS.
728
805
  - Add backup-first uninstall and SQLite-safe memory export/import guidance.
729
806
 
730
- [Unreleased]: https://github.com/cairnkeep/cairnkeep/compare/v2.20.0...HEAD
807
+ [Unreleased]: https://github.com/cairnkeep/cairnkeep/compare/v2.22.4...HEAD
808
+ [2.22.4]: https://github.com/cairnkeep/cairnkeep/compare/v2.22.3...v2.22.4
731
809
  [2.20.0]: https://github.com/cairnkeep/cairnkeep/compare/v2.19.1...v2.20.0
732
810
  [2.19.1]: https://github.com/cairnkeep/cairnkeep/compare/v2.19.0...v2.19.1
733
811
  [2.19.0]: https://github.com/cairnkeep/cairnkeep/compare/v2.18.0...v2.19.0
@@ -1,22 +1,6 @@
1
- import { readFileSync } from "node:fs";
2
-
3
- const port = process.env.MCP_HTTP_PORT?.trim();
4
- if (!port) {
5
- process.exit(0);
6
- }
7
-
8
- let token = process.env.CAIRN_MEMORY_HTTP_TOKEN?.trim();
9
- const tokenFile = process.env.CAIRN_MEMORY_HTTP_TOKEN_FILE?.trim();
10
- if (!token && tokenFile) {
11
- try {
12
- token = readFileSync(tokenFile, "utf8").trim();
13
- } catch {
14
- process.exit(1);
15
- }
16
- }
17
- if (!token) {
18
- process.exit(1);
19
- }
1
+ import { realpathSync } from "node:fs";
2
+ import { pathToFileURL } from "node:url";
3
+ import { parseHttpPort, resolveHttpToken } from "../mcp-memory-server/dist/http-security.js";
20
4
 
21
5
  const body = {
22
6
  jsonrpc: "2.0",
@@ -29,18 +13,28 @@ const body = {
29
13
  },
30
14
  };
31
15
 
32
- try {
33
- const response = await fetch(`http://127.0.0.1:${port}/mcp`, {
34
- method: "POST",
35
- headers: {
36
- Authorization: `Bearer ${token}`,
37
- "Content-Type": "application/json",
38
- Accept: "application/json, text/event-stream",
39
- },
40
- body: JSON.stringify(body),
41
- signal: AbortSignal.timeout(4000),
42
- });
43
- process.exit(response.ok ? 0 : 1);
44
- } catch {
45
- process.exit(1);
16
+ export async function containerHealthcheck(env = process.env) {
17
+ const port = parseHttpPort(env.MCP_HTTP_PORT);
18
+ if (!port.enabled) return "error" in port ? 1 : 0;
19
+ const token = resolveHttpToken(env);
20
+ if (!token.ok) return 1;
21
+ try {
22
+ const response = await fetch(`http://127.0.0.1:${port.port}/mcp`, {
23
+ method: "POST",
24
+ headers: {
25
+ Authorization: `Bearer ${token.token}`,
26
+ "Content-Type": "application/json",
27
+ Accept: "application/json, text/event-stream",
28
+ },
29
+ body: JSON.stringify(body),
30
+ redirect: "manual",
31
+ signal: AbortSignal.timeout(4000),
32
+ });
33
+ await response.body?.cancel();
34
+ return response.ok ? 0 : 1;
35
+ } catch { return 1; }
36
+ }
37
+
38
+ if (process.argv[1] && pathToFileURL(realpathSync(process.argv[1])).href === import.meta.url) {
39
+ process.exitCode = await containerHealthcheck();
46
40
  }
@@ -9,7 +9,7 @@ ContainerName=cairnkeep
9
9
  Exec=http
10
10
  UserNS=keep-id:uid=10001,gid=10001
11
11
  Volume=cairnkeep-data.volume:/data:Z
12
- Secret=cairnkeep-http-token,type=mount,target=http-token
12
+ Secret=cairnkeep-http-token,type=mount,target=http-token,uid=10001,gid=10001,mode=0400
13
13
  Environment=MCP_HTTP_HOST=0.0.0.0
14
14
  Environment=MCP_HTTP_PORT=7801
15
15
  Environment=CAIRN_MEMORY_HTTP_ALLOWED_HOSTS=localhost:7801,127.0.0.1:7801
@@ -80,6 +80,18 @@ cairn-container http \
80
80
 
81
81
  The container always adds its own loopback address for health checks.
82
82
 
83
+ The health-check security follow-up validates `MCP_HTTP_PORT` with the server's
84
+ numeric port parser and uses its bounded private token resolver. Invalid
85
+ configuration fails without issuing a request; probes target only
86
+ `127.0.0.1` and refuse redirects. The existing
87
+ `/usr/local/lib/cairnkeep/container-healthcheck.mjs` command remains available
88
+ as an alias to the packaged probe. Stdio mode requires no HTTP probe.
89
+ File-based probes require a private token readable by the container user;
90
+ world-readable secret mounts fail closed. The Quadlet example explicitly mounts
91
+ its secret as UID/GID 10001 with mode `0400`. For other engines, verify the
92
+ effective in-container ownership and mode rather than relying on a secret
93
+ declaration that the engine may not honor.
94
+
83
95
  One HTTP server and bearer token form one trust domain. They are not a
84
96
  multi-tenant authorization boundary; see [Memory storage and
85
97
  deployment](storage.md).
@@ -32,6 +32,17 @@ work exactly the same without them.
32
32
  | `CAIRN_ANYTHINGLLM_PROJECTS_FILE` | Optional config path for the bundled sync script. Default `${XDG_CONFIG_HOME:-~/.config}/cairnkeep/anythingllm-projects.json`. |
33
33
  | `CAIRN_ANYTHINGLLM_STATE_FILE` | Optional state path for the bundled sync script. Default `${XDG_STATE_HOME:-~/.local/state}/cairnkeep/anythingllm-sync.json`. |
34
34
 
35
+ The query security follow-up validates HTTP(S) URLs and rejects embedded
36
+ credentials, query strings and fragments before fetching. Redirects are not
37
+ followed, and workspace names cannot be empty or URL dot segments. Responses
38
+ are streamed under an 8 MiB cap and must be UTF-8 JSON
39
+ objects; successful answer-field selection is unchanged. Failed requests
40
+ report only a controlled failure class or HTTP status, never a provider error
41
+ body or raw network/stream diagnostic. The existing two-minute query deadline
42
+ remains. Configure HTTPS outside loopback: remote plaintext HTTP remains
43
+ accepted for compatibility, not as a recommendation. Document synchronization
44
+ uses a separate script and does not inherit these query-specific guarantees.
45
+
35
46
  The workspaces the tools target come from the project's **memory config** — the
36
47
  first of `.agent/memory.json`, `.opencode/memory.json`, `.claude/memory.json`, or
37
48
  `memory.json` that exists:
@@ -117,6 +128,10 @@ CAIRN_OPENVIKING_BASE_URL=http://127.0.0.1:1933
117
128
 
118
129
  Plain HTTP is limited to loopback. Use HTTPS for any other host. Credentials in
119
130
  the URL, redirects, invalid response shapes, and oversized bodies are rejected.
131
+ The response cap is 1 MiB. Network and stream failures are payload-free in the
132
+ security follow-up, including failures during response cleanup.
133
+ Shorthand workspace names and explicit `viking://resources` URIs share the
134
+ same decoded traversal checks before any request is sent.
120
135
  Authenticated remote MCP use requires the additional explicit consent setting
121
136
  `CAIRN_OPENVIKING_MCP_HTTP=1`.
122
137
 
@@ -1,6 +1,6 @@
1
1
  # Curriculum Coverage Map
2
2
 
3
- **Baseline:** Cairnkeep 2.22.3
3
+ **Baseline:** Cairnkeep 2.22.5
4
4
  **Last reviewed:** 2026-10-07
5
5
 
6
6
  This map prevents the course from drifting behind the product. It assigns each
@@ -1,6 +1,6 @@
1
1
  # Cairnkeep feature guide
2
2
 
3
- **Baseline:** Cairnkeep 2.22.3
3
+ **Baseline:** Cairnkeep 2.22.5
4
4
 
5
5
  This is the step-back map for choosing and operating Cairnkeep features. The
6
6
  [course labs](https://github.com/cairnkeep/cairnkeep-course-labs) provide one
@@ -3,7 +3,7 @@
3
3
  **Status:** Ready
4
4
  **Track:** Quickstart
5
5
  **Time:** 10 minutes
6
- **Tested with:** Cairnkeep 2.22.3
6
+ **Tested with:** Cairnkeep 2.22.5
7
7
 
8
8
  ## Outcome
9
9
 
@@ -3,7 +3,7 @@
3
3
  **Status:** Ready
4
4
  **Track:** Quickstart
5
5
  **Time:** 15 minutes
6
- **Tested with:** Cairnkeep 2.22.3
6
+ **Tested with:** Cairnkeep 2.22.5
7
7
 
8
8
  ## Outcome
9
9
 
@@ -26,7 +26,7 @@ ephemeral; the named course volume is persistent.
26
26
  1. Pull the public image:
27
27
 
28
28
  ```bash
29
- podman pull ghcr.io/cairnkeep/cairnkeep:2.22.3
29
+ podman pull ghcr.io/cairnkeep/cairnkeep:2.22.5
30
30
  ```
31
31
 
32
32
  2. Start the stdio server with a read-only root filesystem and no Linux
@@ -38,7 +38,7 @@ ephemeral; the named course volume is persistent.
38
38
  --read-only --cap-drop=all --security-opt=no-new-privileges \
39
39
  --tmpfs=/tmp:rw,noexec,nosuid,size=64m,mode=1777 \
40
40
  --volume cairnkeep-course-data:/data:Z,U \
41
- ghcr.io/cairnkeep/cairnkeep:2.22.3 stdio
41
+ ghcr.io/cairnkeep/cairnkeep:2.22.5 stdio
42
42
  ```
43
43
 
44
44
  3. The process waits quietly for MCP input. Press `Ctrl-C` after confirming it
@@ -3,7 +3,7 @@
3
3
  **Status:** Ready
4
4
  **Track:** Quickstart
5
5
  **Time:** 25 minutes
6
- **Tested with:** Cairnkeep 2.22.3 and Node.js 22 or newer
6
+ **Tested with:** Cairnkeep 2.22.5 and Node.js 22 or newer
7
7
 
8
8
  ## Outcome
9
9
 
@@ -47,7 +47,7 @@ sync. Codex project trust remains an explicit operator decision.
47
47
  2. Install Cairnkeep:
48
48
 
49
49
  ```bash
50
- npm install --global @cairnkeep/cli@2.22.3
50
+ npm install --global @cairnkeep/cli@2.22.5
51
51
  cairn --version
52
52
  ```
53
53
 
@@ -3,7 +3,7 @@
3
3
  **Status:** Ready
4
4
  **Track:** Quickstart
5
5
  **Time:** 25 minutes
6
- **Tested with:** Cairnkeep 2.22.3
6
+ **Tested with:** Cairnkeep 2.22.5
7
7
 
8
8
  ## Outcome
9
9
 
@@ -3,7 +3,7 @@
3
3
  **Status:** Ready
4
4
  **Track:** Evidence and Evaluation
5
5
  **Time:** 50 minutes
6
- **Tested with:** Cairnkeep 2.22.3
6
+ **Tested with:** Cairnkeep 2.22.5
7
7
 
8
8
  ## Outcome
9
9
 
@@ -1,7 +1,7 @@
1
1
  # L20 - Least-authority MCP tool profiles
2
2
 
3
3
  **Status:** Ready
4
- **Tested with:** Cairnkeep 2.22.3 and Node.js 22 or newer
4
+ **Tested with:** Cairnkeep 2.22.5 and Node.js 22 or newer
5
5
  **Time:** 25 minutes
6
6
 
7
7
  ## Outcome
@@ -1,7 +1,7 @@
1
1
  # L21 - Immutable context packs and skill approval
2
2
 
3
3
  **Status:** Ready
4
- **Tested with:** Cairnkeep 2.22.3 and Node.js 22 or newer
4
+ **Tested with:** Cairnkeep 2.22.5 and Node.js 22 or newer
5
5
  **Time:** 40 minutes
6
6
 
7
7
  ## Outcome
@@ -3,7 +3,7 @@
3
3
  **Status:** Ready
4
4
  **Track:** Operator
5
5
  **Time:** 30 minutes
6
- **Tested with:** Cairnkeep 2.22.3, Windows x64, and Node.js 22 or newer
6
+ **Tested with:** Cairnkeep 2.22.5, Windows x64, and Node.js 22 or newer
7
7
 
8
8
  ## Outcome
9
9
 
@@ -16,7 +16,7 @@ perform a reversible uninstall without relying on WSL or Git Bash.
16
16
  From PowerShell:
17
17
 
18
18
  ```powershell
19
- npm install --global @cairnkeep/cli@2.22.3
19
+ npm install --global @cairnkeep/cli@2.22.5
20
20
  cairn version
21
21
 
22
22
  $Lab = Join-Path $env:TEMP 'Cairnkeep Windows Lab'
@@ -3,7 +3,7 @@
3
3
  **Status:** Ready
4
4
  **Track:** Quickstart and Operator
5
5
  **Time:** 35 minutes
6
- **Tested with:** Cairnkeep 2.22.3; Pi 0.84.1; Node.js 22, 24, and 26
6
+ **Tested with:** Cairnkeep 2.22.5; Pi 0.84.1; Node.js 22, 24, and 26
7
7
 
8
8
  ## Outcome
9
9
 
@@ -1,7 +1,7 @@
1
1
  # L24 - Reviewed knowledge exchange
2
2
 
3
3
  **Status:** Ready
4
- **Tested with:** Cairnkeep 2.22.3 and Node.js 22 or newer
4
+ **Tested with:** Cairnkeep 2.22.5 and Node.js 22 or newer
5
5
  **Time:** 35 minutes
6
6
 
7
7
  ## Outcome
@@ -1,7 +1,7 @@
1
1
  # L25 - Bounded workflow playbooks
2
2
 
3
3
  **Status:** Ready
4
- **Tested with:** Cairnkeep 2.22.3 and Node.js 22 or newer
4
+ **Tested with:** Cairnkeep 2.22.5 and Node.js 22 or newer
5
5
  **Time:** 35 minutes
6
6
 
7
7
  ## Outcome
@@ -1,7 +1,7 @@
1
1
  # L26 - Context intelligence with explicit authority
2
2
 
3
3
  **Status:** Ready
4
- **Tested with:** Cairnkeep 2.22.3 and Node.js 22 or newer
4
+ **Tested with:** Cairnkeep 2.22.5 and Node.js 22 or newer
5
5
 
6
6
  ## Outcome
7
7
 
@@ -1,6 +1,6 @@
1
1
  # L27 - Evaluate context companions without moving authority
2
2
 
3
- **Tested with:** Cairnkeep 2.22.3 and Node.js 22 or newer
3
+ **Tested with:** Cairnkeep 2.22.5 and Node.js 22 or newer
4
4
 
5
5
  ## Outcome
6
6
 
@@ -1,7 +1,7 @@
1
1
  # L28 - Verify Cairnkeep's security posture
2
2
 
3
3
  **Status:** Ready
4
- **Tested with:** Cairnkeep 2.22.3 and Node.js 22 or newer
4
+ **Tested with:** Cairnkeep 2.22.5 and Node.js 22 or newer
5
5
 
6
6
  ## Outcome
7
7
 
@@ -11,7 +11,7 @@ and choose least authority for a network-facing MCP client.
11
11
 
12
12
  ## Prerequisites
13
13
 
14
- - Install Cairnkeep 2.22.3 or use a disposable source checkout.
14
+ - Install Cairnkeep 2.22.5 or use a disposable source checkout.
15
15
  - Complete [L20 - Least-authority MCP tool profiles](L20-mcp-tool-profiles.md).
16
16
  - Use synthetic values only; never paste a real bearer token into a recording,
17
17
  terminal transcript, or issue.
@@ -68,6 +68,16 @@ scripts/test-security-baseline.sh
68
68
  npm --prefix mcp-memory-server run check:security-assurance
69
69
  npm --prefix mcp-memory-server run check:http-guard
70
70
  node mcp-memory-server/scripts/smoke-context-pack-state.mjs
71
+ node mcp-memory-server/scripts/smoke-note-signatures.mjs
72
+ node mcp-memory-server/scripts/smoke-runtime-file-reads.mjs
73
+ node mcp-memory-server/scripts/smoke-bounded-input-reads.mjs
74
+ node mcp-memory-server/scripts/smoke-workspace-file-security.mjs
75
+ node mcp-memory-server/scripts/smoke-outbound-security.mjs
76
+ node scripts/test-runtime-file-security.mjs
77
+ node scripts/test-omp-staged-security.mjs
78
+ node scripts/test-container-health.mjs
79
+ node mcp-memory-server/scripts/smoke-note-mcp.mjs --fixture-security-only
80
+ node scripts/verify-phase19-runtime-evidence.mjs --self-test
71
81
  ```
72
82
 
73
83
  The baseline rejects unpinned external Actions, `pull_request_target` and
@@ -81,6 +91,15 @@ These are static contract checks, not a live publication test. The npm secret
81
91
  is a separate credential, and steps within one job share its token authority.
82
92
  Do not republish an immutable version to demonstrate the test.
83
93
 
94
+ For post-publication evidence, run `node scripts/test-release-verification.mjs`
95
+ and read [Repeatable post-publication verification](../../releasing.md#repeatable-post-publication-verification).
96
+ The offline mutations reject mismatched trees/assets and failed signature or
97
+ provenance decisions; they do not validate real signatures. The explicit live
98
+ verifier checks the published artifacts with npm and the GitHub CLI and writes
99
+ a new report only after success. Its report says `deployment_verified: false`:
100
+ server backups, canaries and project doctors remain separate rollout evidence.
101
+ Never publish or upgrade a service just to record this exercise.
102
+
84
103
  The smoke tests cover token non-disclosure, request bounds, and deterministic
85
104
  portable-path adversarial cases.
86
105
 
@@ -91,6 +110,57 @@ under bounded reads. A resolved review thread or green CodeQL job does not mean
91
110
  all historical alerts are fixed: inspect current findings and test the actual
92
111
  boundary without dismissing results merely to improve an advisory score.
93
112
 
113
+ In a checkout containing the runtime-security follow-up, the signature control
114
+ keeps existing v1 fingerprints byte-equivalent and isolates crafted path inputs
115
+ behind a five-second child-process timeout. The runtime file-read control
116
+ substitutes valid capability configuration, skill targets/ledgers/backups and
117
+ work-evidence records after inspection. Readers reject those replacements,
118
+ bound growth, reject special files without waiting for a writer and close their
119
+ descriptors. Ordinary project configuration may remain `0644`; private evidence
120
+ and approval ledgers still require private ownership/ACLs. Build the server
121
+ before running source-checkout controls. The generated parser corpus is
122
+ reproducible regression coverage, not integrated coverage-guided fuzzing.
123
+
124
+ The bounded-input follow-up extends descriptor-bound reads to evaluation,
125
+ OKF import/export, graph artifacts, private playbook receipts and derived
126
+ progressive-context caches. Compare a replaced valid file with same-inode
127
+ growth: rejecting oversized content after `readFile` has allocated it is not
128
+ a bounded read. Note snapshots publish the bytes that were inspected and
129
+ hashed, not a later reopened file. Unsafe derived caches can be rebuilt from
130
+ immutable content; read-only doctor reports them without deletion. Cache
131
+ rebuilding never changes project enablement or skill approval. This exercise
132
+ does not certify directory-wide transactions against other same-account
133
+ processes.
134
+
135
+ The publication follow-up extends the exercise to setup, instruction files,
136
+ evaluation overlays and native Windows managed writes. Inspect a concurrent
137
+ caller edit: rechecking the observed target before publication must preserve
138
+ it. A denied Windows replacement must retain the live database, not unlink it
139
+ to retry. A substituted overlay symlink must leave its outside sentinel intact.
140
+ Exclusive descriptor-written temporaries retain modes and clean only their own
141
+ files. These are point-in-time safeguards, not all-project transactions or
142
+ protection against another hostile process using your account. Native Windows
143
+ and container/package verification remain required before shipping.
144
+
145
+ The OMP control reads only synthetic staged candidates, never invokes a model,
146
+ and shows that linked, growing or over-8-KiB content cannot become wakeup
147
+ evidence or `/cairn-staged` output. It does not approve or persist a candidate.
148
+ The outbound and health controls stub network calls: provider redirects and
149
+ oversized bodies fail, private error sentinels never appear in diagnostics,
150
+ and a malformed health port never becomes a network destination. Successful
151
+ retrieval still sends a query to the explicitly configured external provider.
152
+ AnythingLLM responses have an 8 MiB cap; OpenViking retains its 1 MiB cap and
153
+ separate consent gates. Prefer HTTPS outside loopback.
154
+
155
+ In a checkout with the developer-evidence follow-up, the focused note control
156
+ shows that exclusive fixture creation preserves a competing file and that the
157
+ actual note transaction rejects a substituted pre-image. Hashing is bounded
158
+ at 64 MiB, including same-inode growth checks. The runtime-evidence self-test
159
+ rejects changed or replaced log files under its existing 16 MiB cap. Developer
160
+ snapshots and corrupt-database controls still compare exact bytes; static log
161
+ labels cannot turn provider text into forged passing results. This does not
162
+ make multi-file note publication atomic or prove an empty analysis backlog.
163
+
94
164
  ## Common failures
95
165
 
96
166
  - A private `.ai` file is group/world accessible on POSIX: restore mode `0600`.
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Audience:** advanced users and platform maintainers
4
4
  **Prerequisite:** Practitioner track; L13 before operating captured evidence
5
- **Total time:** about 6 hours 45 minutes
5
+ **Total time:** about 6 hours 45 minutes, plus optional evidence/security supplements
6
6
 
7
7
  1. [L09 - Domain knowledge with RAG](../lessons/L09-domain-knowledge.md)
8
8
  2. [L10 - Faster context exploration](../lessons/L10-context-exploration.md)
@@ -17,6 +17,8 @@
17
17
  11. [L24 - Reviewed knowledge exchange](../lessons/L24-okf-exchange.md)
18
18
  12. [L25 - Bounded workflow playbooks](../lessons/L25-playbooks.md)
19
19
  13. [L26 - Context intelligence with explicit authority](../lessons/L26-context-intelligence.md)
20
+ 14. [L27 - Evaluate ecosystem ideas with bounded evidence](../lessons/L27-ecosystem-evidence.md)
21
+ 15. [L28 - Verify Cairnkeep's security posture](../lessons/L28-security-assurance.md)
20
22
 
21
23
  All modules are optional. The track is complete when the operator can add and
22
24
  remove each integration without breaking the standalone local workflow and can
@@ -27,6 +29,9 @@ L24 adds portable exchange only after the immutable-pack and privacy boundaries
27
29
  are understood.
28
30
  L25 adds policy enforcement and the unauthenticated local actor boundary before
29
31
  any future team deployment.
32
+ L27 separates protocol observations from measured task benefits. L28 adds
33
+ deployment posture, adversarial source-checkout controls and exact-source
34
+ release verification; a verified artifact is not a verified fleet rollout.
30
35
 
31
36
  Hands-on spine: `course-04-operation`, `course-06-governance`, and
32
37
  `course-07-evaluation`, `course-10-trust-context`, and
@@ -3,6 +3,11 @@
3
3
  Target length: 10–12 minutes. Audience: experienced developers operating an
4
4
  agent-memory service locally or behind authenticated HTTP.
5
5
 
6
+ The source-checkout security follow-up blocks below are optional supplemental
7
+ clips, not part of the original 10–12-minute timing. Record them as a separate
8
+ segment after their branch passes the complete release gates; do not present
9
+ unreleased controls as installed-package behavior.
10
+
6
11
  ## Recording outline
7
12
 
8
13
  ### 0:00–1:20 — Threat model before tooling
@@ -69,6 +74,46 @@ rejection, bounded growth and cleanup; use no real private pointer/approval
69
74
  data. Distinguish resolved old review comments from current analysis findings,
70
75
  and do not imply a green CI run certifies an empty security backlog.
71
76
 
77
+ For the runtime-security follow-up, show
78
+ `node mcp-memory-server/scripts/smoke-note-signatures.mjs` and
79
+ `node mcp-memory-server/scripts/smoke-runtime-file-reads.mjs` from a built source
80
+ checkout. Explain that a tiny crafted path used to stall signature parsing,
81
+ while the fixed parser preserves the existing v1 fixture fingerprints. The
82
+ child-process timeout makes a regression fail instead of freezing the demo.
83
+ Then show valid-file replacement rejection through capability, skill and
84
+ work-evidence readers. Explain fixed byte bounds and descriptor cleanup, and
85
+ distinguish private ledger permissions from ordinary project-file modes. Do not
86
+ call the generated corpus coverage-guided fuzzing or claim that this patch
87
+ resolves every historical analysis alert. Use only the synthetic fixtures.
88
+
89
+ In a checkout with the bounded-input follow-up, run
90
+ `node mcp-memory-server/scripts/smoke-bounded-input-reads.mjs`. Zoom into
91
+ valid-file substitution and same-inode growth through evaluation, OKF, graph,
92
+ receipt and cache entry points. Contrast a byte cap checked after allocation
93
+ with a fixed descriptor-read allocation. Show that note snapshots publish the
94
+ validated bytes and rejected derived cache data is rebuilt, while read-only
95
+ doctor never deletes it. Explain that this does not enable a pack or approve a
96
+ skill, make directory-wide publication transactional, or substitute for native
97
+ Windows verification. Keep a pause between the failure and remediation.
98
+
99
+ For the publication follow-up, show
100
+ `node scripts/test-runtime-file-security.mjs` beside
101
+ `node mcp-memory-server/scripts/smoke-workspace-file-security.mjs`. Pause on the
102
+ concurrent caller edit and denied-replacement controls: the previous content
103
+ must survive. Contrast a direct path write with an exclusive, flushed temporary
104
+ file followed by publication. State that immediate parent/target checks are
105
+ not directory-wide transactions or a same-account sandbox. Native Windows
106
+ verification is still required; POSIX fixture execution is not proof of it.
107
+
108
+ Run `node scripts/test-omp-staged-security.mjs` and explain the 8 KiB candidate
109
+ bound, symlink exclusion and unchanged agent-review requirement. Then show
110
+ `node mcp-memory-server/scripts/smoke-outbound-security.mjs` and
111
+ `node scripts/test-container-health.mjs`: synthetic provider errors must stay
112
+ out of diagnostics, redirects are refused, response bytes are bounded, and
113
+ the health probe cannot turn an unvalidated port into an outside destination.
114
+ These controls stub network calls; never show real credentials or imply that
115
+ the exercise contacted a provider. Leave a reading pause after each result.
116
+
72
117
  Briefly open `.github/workflows/publish.yml` beside the permission table in
73
118
  `docs/releasing.md`. Contrast the read-only preparation job with the necessary
74
119
  npm and OCI publication grants. Explain that explicit maps deny unspecified
@@ -76,6 +121,26 @@ permissions, but steps in a job still share authority and the npm secret is
76
121
  separate. The baseline checks declared grants; do not present it as a successful
77
122
  live publish or rerun a release merely for this recording.
78
123
 
124
+ In the release-verification supplement, run
125
+ `node scripts/test-release-verification.mjs` using only the synthetic corpus.
126
+ Show a mismatched tree, altered asset and failed provenance decision, then
127
+ contrast offline policy coverage with the explicitly invoked live verifier in
128
+ `docs/releasing.md`. Show only public artifact metadata from an already
129
+ completed release, never tokens or raw diagnostics. Pause on
130
+ `deployment_verified: false`: signed artifact evidence does not prove that a
131
+ server was upgraded or its project routes still work. Do not publish or
132
+ upgrade production for a demonstration.
133
+
134
+ For the developer-evidence supplement, run
135
+ `node mcp-memory-server/scripts/smoke-note-mcp.mjs --fixture-security-only` and
136
+ `node scripts/verify-phase19-runtime-evidence.mjs --self-test` in a built source
137
+ checkout. Pause on the competing-file preservation and actual transaction
138
+ pre-image replacement controls. Explain that hashing uses one bounded inspected
139
+ descriptor, not a pathname check followed by reopening. Contrast integrity of
140
+ a read with atomicity of a multi-file transaction: this patch improves the
141
+ former, not the latter. Use synthetic files and keep historical alert status
142
+ separate from a green test result; record only after the release gates pass.
143
+
79
144
  ### 9:20–10:30 — Read the residual risks
80
145
 
81
146
  End on `docs/security-assurance.md`, not on a green checkmark. Call out the
@@ -125,6 +125,16 @@ access, or background synchronization.
125
125
  uses `sqlite3.exe` `.backup` for a consistent snapshot of each live WAL database
126
126
  and then writes the same portable `.tgz` format used on Unix.
127
127
 
128
+ The archive/publication security follow-up binds compressed input to one
129
+ descriptor with a 512 MiB cap; decompressed tar output is independently capped
130
+ at 512 MiB. Managed writes use exclusive descriptor-written temporary files,
131
+ flush them and recheck the immediate parent/target before publication. Sharing
132
+ violations get bounded retries with at most 250 ms total backoff. A denied
133
+ replacement never unlinks the live destination; its prior contents survive.
134
+ These checks do not make a multi-database import transactional, and a post-write
135
+ ACL failure is not a rollback. Keep the pre-import backup and diagnose the
136
+ failure before retrying.
137
+
128
138
  ## Scheduling and process cleanup
129
139
 
130
140
  `cairn audit-timer` registers the opt-in `Cairnkeep Memory Audit` task through