@josfox/jos 4.0.8 → 5.0.0

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 (59) hide show
  1. package/LICENSE +208 -21
  2. package/PROMISES.md +52 -0
  3. package/README.md +294 -146
  4. package/SECURITY-MCP.md +117 -0
  5. package/SECURITY-SECRETS.md +108 -0
  6. package/STYLE.md +23 -0
  7. package/bin/jos +139 -67
  8. package/bin/jos.cmd +5 -0
  9. package/examples/01-hello.jos +44 -0
  10. package/examples/02-criteria.jos +54 -0
  11. package/examples/03-flow-gate.jos +83 -0
  12. package/examples/04-use-template/README.md +22 -0
  13. package/examples/04-use-template/lib/greeting.jos +56 -0
  14. package/examples/04-use-template/use.jos +64 -0
  15. package/examples/05-sealed/.jos/manifest.jos +53 -0
  16. package/examples/05-sealed/README.md +25 -0
  17. package/examples/06-requires.jos +42 -0
  18. package/examples/07-sandbox.jos +39 -0
  19. package/examples/08-ids-ledger-meter.md +26 -0
  20. package/examples/10-ollama.jos +47 -0
  21. package/examples/11-openai.jos +47 -0
  22. package/examples/12-gemini.jos +47 -0
  23. package/examples/13-anthropic.jos +47 -0
  24. package/examples/14-serve.jos +88 -0
  25. package/examples/README.md +105 -0
  26. package/examples/hello.jos +47 -0
  27. package/package.json +31 -9
  28. package/promises.json +894 -0
  29. package/src/args.js +47 -0
  30. package/src/commands/_frozen/add.README.md +1 -0
  31. package/src/commands/_frozen/get.README.md +1 -0
  32. package/src/commands/_frozen/llm.README.md +1 -0
  33. package/src/commands/_frozen/prompts.README.md +1 -0
  34. package/src/commands/_frozen/repo.README.md +1 -0
  35. package/src/commands/_frozen/serve-explorer.README.md +1 -0
  36. package/src/commands/ids.js +101 -0
  37. package/src/commands/init.js +113 -147
  38. package/src/commands/ledger.js +94 -0
  39. package/src/commands/meter.js +96 -0
  40. package/src/commands/migrate.js +214 -0
  41. package/src/commands/overview.js +118 -0
  42. package/src/commands/run.js +164 -214
  43. package/src/commands/runs.js +102 -0
  44. package/src/commands/secrets.js +695 -115
  45. package/src/commands/start.js +210 -0
  46. package/src/commands/validate.js +153 -165
  47. package/src/commands/verify.js +56 -0
  48. package/src/commands/version.js +23 -0
  49. package/src/intention.js +114 -0
  50. package/src/kernel.js +69 -0
  51. package/src/ledgers.js +57 -0
  52. package/src/mcp-guide.js +138 -0
  53. package/src/mcp.js +734 -0
  54. package/src/serve.js +38 -770
  55. package/src/ui.js +98 -0
  56. package/src/commands/add.js +0 -145
  57. package/src/commands/get.js +0 -245
  58. package/src/commands/repo.js +0 -139
  59. package/src/index.js +0 -0
package/README.md CHANGED
@@ -1,186 +1,334 @@
1
- <div align="center">
1
+ # @josfox/jos
2
+
3
+ A `.jos` is an intention you declare: **what** to do, **when it is done** (success criteria) and **what to avoid**
4
+ (guardrails). It is **validated without running anything**, and **run with guarantees**: limits enforced while it
5
+ runs, every criterion checked, a receipt per step and a hash-chained ledger per run. It has a **content identity**
6
+ (`art_`, the hash of the document; JOSID names such as `ent_`). It is **fractal**: an atom, a flow of atoms and a
7
+ folder with a `.jos/` directory have the same shape. And it is **reusable**: `use` another document by id,
8
+ instantiate templates, pin what you depend on by digest. Two reference kernels, JavaScript and Python, give
9
+ identical results on every conformance vector.
10
+
11
+ `jos` is the command line for this. It hands every document to the reference kernel `jos-core` and shows what the
12
+ kernel decided; it has no execution logic of its own.
13
+
14
+ Version 5.0.0 · kernel `@josfox/jos-core` 2.0.0 (its only dependency) · Node.js 18 or later ·
15
+ `jos` and `jos-core` open no network connection (a step you write can; see [Security](#security)) ·
16
+ Apache-2.0.
17
+
18
+ ## Install
2
19
 
3
20
  ```
4
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡀⠀⠀⠀
5
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⠙⠻⢶⣄⡀⠀⠀⠀⢀⣤⠶⠛⠛⡇⠀⠀⠀
6
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣇⠀⠀⣙⣿⣦⣤⣴⣿⣁⠀⠀⣸⠇⠀⠀⠀
7
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣡⣾⣿⣿⣿⣿⣿⣿⣿⣷⣌⠋⠀⠀⠀⠀
8
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣷⣄⡈⢻⣿⡟⢁⣠⣾⣿⣦⠀⠀⠀⠀
9
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣿⣿⣿⣿⠘⣿⠃⣿⣿⣿⣿⡏⠀⠀⠀⠀
10
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠈⠛⣰⠿⣆⠛⠁⠀⡀⠀⠀⠀⠀⠀
11
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⣿⣦⠀⠘⠛⠋⠀⣴⣿⠁⠀⠀⠀⠀⠀
12
- ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣶⣾⣿⣿⣿⣿⡇⠀⠀⠀⢸⣿⣏⠀⠀⠀⠀⠀⠀
13
- ⠀⠀⠀⠀⠀⠀⣠⣶⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⠀⠀⠀⠾⢿⣿⠀⠀⠀⠀⠀⠀
14
- ⠀⠀⠀⠀⣠⣿⣿⣿⣿⣿⣿⡿⠟⠋⣁⣠⣤⣤⡶⠶⠶⣤⣄⠈⠀⠀⠀⠀⠀⠀
15
- ⠀⠀⠀⢰⣿⣿⣮⣉⣉⣉⣤⣴⣶⣿⣿⣋⡥⠄⠀⠀⠀⠀⠉⢻⣄⠀⠀⠀⠀⠀
16
- ⠀⠀⠀⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣟⣋⣁⣤⣀⣀⣤⣤⣤⣤⣄⣿⡄⠀⠀⠀⠀
17
- ⠀⠀⠀⠀⠙⠿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠛⠋⠉⠁⠀⠀⠀⠀⠈⠛⠃⠀⠀⠀⠀
18
- ⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
21
+ npm install -g @josfox/jos
22
+ jos --version # jos 5.0.0 and the jos-core it found
19
23
  ```
20
24
 
21
- # @josfox/jos
22
-
23
- **Format version v0.0.7 — Specification maturity v0.1.0 (Alpha)**
25
+ `@josfox/jos` needs `@josfox/jos-core` from the registry: both packages have to be published for this to install.
26
+ `jos` finds the kernel as the installed `@josfox/jos-core`, else as `kernel/js/jos-core.js` of a checkout of the
27
+ repository; without one, `run`, `validate` and the other kernel commands exit 5 and say so.
24
28
 
25
- **Stoic Kernel for AI Agent Orchestration**
29
+ ## Start
26
30
 
27
- [![npm](https://img.shields.io/badge/npm-@josfox/jos-00ffff?style=for-the-badge)](https://www.npmjs.com/package/@josfox/jos)
28
- [![Version](https://img.shields.io/badge/version-4.0.4-success?style=for-the-badge)](#)
29
- [![Dependencies](https://img.shields.io/badge/dependencies-0-blue?style=for-the-badge)](#zero-dependencies)
30
- [![License](https://img.shields.io/badge/license-MIT-purple?style=for-the-badge)](LICENSE)
31
+ ```
32
+ jos # what jos does, and the .jos documents in this folder (read only)
33
+ jos start # writes ./jos-start: four examples that need no keys and no network
34
+ cd jos-start && jos run hello.jos
35
+ ```
31
36
 
32
- *Zero Dependencies • Offline First • Open Standard*
37
+ `jos` alone lists the `.jos` / `.jos.json` documents of the current folder and one folder down, and the folders
38
+ with a `.jos/manifest.jos`, each with its `jos validate` and `jos run` command. It runs nothing, writes nothing, and
39
+ never scans your home folder. `jos start [dir]` writes `hello.jos` (an atom), `flow.jos` (a flow with a human gate),
40
+ `sealed/` (a folder sealed by its hash) and `limits.jos` (guardrails stopping two steps), plus a README with the
41
+ commands in order and what each prints; it never overwrites a file.
33
42
 
34
- </div>
43
+ `jos run` shows the intention first, then the verdict of each criterion, then the outcome:
35
44
 
36
- ---
45
+ ```
46
+ $ jos run hello.jos
37
47
 
38
- ## Overview
48
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
49
+ JOS RUN // jos-core 2.0.0
50
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
39
51
 
40
- **@josfox/jos** is the reference implementation of the **.jos Open Standard** — a portable artifact format for AI agent orchestration.
52
+ File: hello.jos
41
53
 
42
- | Feature | Description |
43
- |---------|-------------|
44
- | **Stoic Kernel** | 77-line minimal kernel with zero dependencies |
45
- | **Plugin Architecture** | Commands live in `~/.jos/commands/` or bundled |
46
- | **Offline First** | Works without network, like Git |
47
- | **A2A Compatible** | Designed for Agent-to-Agent interoperability |
54
+ Intention: Print a greeting and exit cleanly
55
+ Done when:
56
+ • exit 0
57
+ • stdout contains "hello"
58
+ Avoid: "rm -rf", "curl", "wget"
59
+ Limit: 10 s, 65536 output bytes per stream, 0 retries
48
60
 
49
- ---
61
+ ID: art_803576e37d948d3036d1c84febcfc229f6fabc78ffebdbd1a427e9ca7d342575
62
+ Input version: 2.0.0
63
+ Capability: cap_… (local-trusted)
64
+ Ledger: ~/.jos/runs/run_…/ledger.jsonl
50
65
 
51
- ## Quick Start
66
+ hello, .jos
52
67
 
53
- ```bash
54
- # Run with npx (no install required)
55
- npx josfox serve
68
+ Verdict:
69
+ [1] hello ✓ code 0
70
+ ✓ exit: exit 0 (want 0)
71
+ ✓ stdout_contains: found
56
72
 
57
- # Or install globally
58
- npm install -g @josfox/jos
59
- jos serve
73
+ ✓ Intention met (code 0, run_…)
60
74
  ```
61
75
 
62
- ---
76
+ When it is not met, the last line says why: `✖ Intention NOT met (code 1: not done)`, with the reason below it.
77
+ `--json` prints the kernel's result object instead, unchanged.
78
+
79
+ ## What it can do
80
+
81
+ Each capability, the command, and the example of the tour (`examples/`, see [its README](examples/README.md)) that
82
+ shows it.
83
+
84
+ | Capability | Command | Tour |
85
+ |---|---|---|
86
+ | Declare an intention: objective, success criteria, `avoid`, `limit` | `jos init <name>`, `jos start`, `jos validate <file> --strict` | `01-hello.jos` |
87
+ | Check it without running anything: JSON, schema, normalization, `art_` | `jos validate <file>` | any |
88
+ | Every criterion evaluated and reported (`exit`, `stdout_contains`, `stdout_matches`, `stderr_empty`, files, hashes, `all` / `any` / `not`; text criteria are recorded, not evaluated) | `jos run <file>` | `02-criteria.jos` |
89
+ | Guardrails before and during the run: an `avoid` match never starts (3), time (124) and output (137) limits stop the process | `jos run limits.jos` (from `jos start`) | `limits.jos` |
90
+ | Flows: sequences and DAGs (`needs`), automatic and human gates, data between steps | `jos run <flow> --approve <step>`, `--flow <name>`, `--input <json>` | `03-flow-gate.jos` |
91
+ | Reuse: `use` by id and version range, templates with typed slots, a dependency pinned by digest | `jos run <flow> --library <dir>`, `jos run <template> --with <json>` | `04-use-template/` |
92
+ | A folder is a document; sealed by its directory hash, refused when anything in `.jos/` changes (3) | `jos run <folder>` | `05-sealed/` |
93
+ | Refuse what it cannot honor, before the first step (5): unmet `requires[]`, `security.tier: sandbox` | `jos run <file>` | `06-requires.jos`, `07-sandbox.jos` |
94
+ | Identity: `art_` of the content, `ent_` of the name, the hash of a folder | `jos ids <file>…` | `08-ids-ledger-meter.md` |
95
+ | Receipts and a hash-chained ledger of every run; verify it; meter it (tokens and cost when an engine reports them) | `jos runs`, `jos ledger verify <run>`, `jos meter <run>` | `08-ids-ledger-meter.md` |
96
+ | Credentials for a step without putting them in the document, the command line or the output | `jos secrets exec --only NAME -- jos run <file>` | `11-openai.jos`, `12-gemini.jos`, `13-anthropic.jos` |
97
+ | A local model as a shell step | `jos run 10-ollama.jos` | `10-ollama.jos` |
98
+ | A read-only page of your runs, on 127.0.0.1 only | `jos run 14-serve.jos` | `14-serve.jos` |
99
+ | Give an MCP client (Claude Code, Claude Desktop, Cursor) read-only access to a project folder | `jos serve --mcp` | see below |
100
+ | Reseal a 4.1.0 document (0.0.7 with `security.integrity_ref`) as 2.0, only if it verifies | `jos migrate <file>` | see Changes since 4.1.0 |
63
101
 
64
102
  ## Commands
65
103
 
66
- | Command | Description |
67
- |---------|-------------|
68
- | `jos serve` | Start development server with dashboard |
69
- | `jos run <file>` | Execute .jos artifacts |
70
- | `jos get <package>` | Fetch packages from repositories |
71
- | `jos secrets` | Manage encrypted credentials (AES-256) |
72
- | `jos repo` | Manage package repositories |
73
-
74
- ```bash
75
- # Examples
76
- jos serve --port 8080
77
- jos run artifact.jos --dry-run
78
- jos get myrepo:package-name
79
- jos secrets set API_KEY sk-abc123
80
- ```
81
-
82
- ---
104
+ | Command | What it does |
105
+ |---|---|
106
+ | `jos` | Overview and read-only discovery of this folder (see Start). Exit 0. |
107
+ | `jos start [dir]` | Writes the four start examples and their README into `dir` (default `jos-start`), each validated by jos-core first; refuses (1) if any of them exists. |
108
+ | `jos run <file \| folder> [--json] [--dry-run] [--flow <name>] [--library <dir>] [--approve <step>]… [--input <json>] [--with <json>]` | Runs the document with jos-core, in the document's own folder (never the shell's current directory). A folder `D` (or `D/.jos`) with `D/.jos/manifest.jos` runs that manifest in `D` when it is of kind `directory` (a 1.0.0 flow with `meta.labels.dotjos` too); any other manifest is 2 before any step, as `jos validate D` says; a sealed folder is verified by its hash first. The options are passed to jos-core as they are: `--flow` picks a flow (default `main`, or the only one), `--library` is one more place for `use`, `--approve` approves a human gate (repeatable; `outer/step` for a nested one, `step#2` for a second occurrence), `--input` is the run's input, `--with` the slots of a template run directly. `--dry-run` checks everything and prints the plan without launching. `--` ends the options. |
109
+ | `jos validate <file> [--json] [--strict]` | Validates and normalizes; prints the `art_`, the input version, the intention and `Inferred:`, what jos-core applies that the document does not write (each JSON Pointer with its value and source: a SPEC section or a jos-core default), with `⚠ … not declared` for a missing objective, success criteria or guardrails. `--json` carries the same `inferred` section as `jos-core validate --json`. `--strict` also exits 2 when the objective, the success criteria or the guardrails are not written in the document. A folder `D` (or `D/.jos`) is validated as a directory by jos-core: a manifest that is not of kind `directory` is 2, the answer of `jos run D`; `--strict` applies to the manifest. Never runs anything. |
110
+ | `jos migrate <file> [--out <file> \| --in-place] [--force]` | For a 0.0.7 document sealed by `security.integrity_ref` (what `jos init` of 4.1.0 wrote): verifies the manifest exactly as `run` does (a mismatch is 3 and nothing is written), then writes `<name>.v2.jos` (or `--out`): jos-core's 2.0 normalization with `integrity_ref` removed, `security.permissions` moved to `security.x-legacy-permissions` (4.1.0 never applied it; 2.0 would apply an empty list as "no shell") and `integrity.hash` of the result. The result must validate and plan the same steps as the original before it is written; it prints what changed. Never overwrites the input without `--in-place`, nor an existing output without `--force`. A 2.0 document: "already 2.0" (0); a 1.1.0 or 1.0.0 document, or a 0.0.7 one without `integrity_ref`: 64 (they run as they are). |
111
+ | `jos ids <file \| folder>… [--json]` | `art_`, `ent_` (`uuid5("<kind>:<id>")`) and, for a folder, its directory hash and whether it matches its seal. Exit 2 if one is invalid. |
112
+ | `jos runs [--limit <n>] [--json]` | The runs in `~/.jos/runs/`, newest first: id, start, code, document, `art_`, metered cost. Each ledger is verified; one that does not verify is flagged. Never creates `~/.jos`. |
113
+ | `jos ledger verify <run_id \| ledger.jsonl \| run folder> [--json]` | Recomputes every entry hash of a run's ledger and checks the chain and the end entry. Exit 0 verifies, 1 does not, 2 not found. |
114
+ | `jos meter <run_id \| ledger.jsonl \| run folder> [--json]` | Steps, engines, attempts, time, tokens, cost, approvals, from the ledger, only when it verifies; a meter no engine reported is "not reported", never 0. |
115
+ | `jos init <name> [--force]` | Writes `<name>.jos`, a 2.0 atom validated by jos-core. A name ending in `.jos` / `.jos.json` gets no second extension; a directory part is kept (it must exist). |
116
+ | `jos verify` | Self-check: runs the embedded `examples/hello.jos` in a new temporary folder (also its HOME) and removes it. |
117
+ | `jos version`, `jos --version` | Host version; kernel version and where it was found. |
118
+ | `jos secrets set\|get\|list\|delete\|exec\|migrate\|fix-perms` | Local credentials in `~/.jos/secrets/`. Values never go on a command line or to stdout: `set NAME --stdin`, `get NAME` shows a keyed fingerprint, `exec --only A,B -- <cmd>` adds only those values to the child's environment. Details: [SECURITY-SECRETS.md](SECURITY-SECRETS.md). |
119
+ | `jos serve --mcp [--root <dir>] [--allow-write] [--allow-shell]` | MCP server over stdio, read-only by default. Details below and in [SECURITY-MCP.md](SECURITY-MCP.md). |
120
+
121
+ Every command has `--help`. `jos <file>` with a `.jos` file or a `.jos/` folder is not a command: it exits 1 and
122
+ names `jos run <file>` and `jos validate <file>`; nothing runs by itself. Every message for a person has one format
123
+ (`✓` / `✖` / `⚠`, color only on a terminal and without `NO_COLOR`, errors on stderr; rules and their test:
124
+ [STYLE.md](STYLE.md)); `--json` output is plain JSON.
125
+
126
+ ### Exit codes
127
+
128
+ | Code | Meaning |
129
+ |---|---|
130
+ | 0 | done |
131
+ | 1 | not done (a criterion failed, a human gate is waiting) |
132
+ | 2 | invalid document |
133
+ | 3 | blocked (`avoid` matched, the Capability denied it, or an integrity hash or a 0.0.7 `integrity_ref` manifest does not match) |
134
+ | 4 | budget exceeded before start |
135
+ | 5 | unexecutable (no kernel, unresolved `use`, unmet `requires[]`, no engine for it, `sandbox` or `privileged` that this kernel cannot honor) |
136
+ | 124 | time limit |
137
+ | 137 | output limit or killed |
138
+ | 64 | usage error |
139
+ | 70 | internal error of the host (never a document outcome); the stack trace only with `JOS_DEBUG=1` |
140
+
141
+ ### Files written
142
+
143
+ `jos` writes only what a command is for: `init` its file, `start` its folder, `migrate` its output file, `secrets` `~/.jos/secrets/`,
144
+ `serve --mcp --allow-write` what the client asks inside its root. jos-core writes a ledger for every `run` (not
145
+ `--dry-run`): `<run_id>/ledger.jsonl` in `.jos/replays/` of the nearest folder above the document that has a
146
+ `.jos/manifest.jos`, otherwise in `~/.jos/runs/`. The ledger holds the Capability, the receipts and hashes; a step's
147
+ output appears in it only as a hash. `jos`, `validate`, `ids`, `runs`, `ledger`, `meter`, `verify`, `version` and
148
+ `run --dry-run` leave nothing on disk.
149
+
150
+ ## Security
151
+
152
+ What `jos` and jos-core guarantee, exactly:
153
+
154
+ - **Validating never executes.** `validate`, `ids`, `jos` alone and `run --dry-run` read and check; no step starts.
155
+ - **`avoid` is checked before launch.** A pattern that matches the engine input (the argv or command line) blocks
156
+ the step before its process exists: code 3. **`limit` is enforced while it runs**: past `seconds` the process
157
+ group is killed (124); past `output_bytes` on a stream it is killed (137); `retries` bounds the correction loop.
158
+ - **Hashes before running.** `integrity.hash` of a document, of a document reached by `use`, of a pinned
159
+ `requires[].digest` and of a sealed folder is recomputed (SHA-256 over the canonical JSON, RFC 8785) before
160
+ anything starts; a mismatch is code 3 (5 for an unmet digest). Signatures are not verified yet:
161
+ `security.tier: privileged` is refused (5).
162
+ - **A 0.0.7 document sealed by `security.integrity_ref`** (4.1.0's separate manifest) is verified against the octets
163
+ `jos run` read, before anything starts, `--dry-run` included: the manifest path must be relative and stay inside the
164
+ document's folder (no absolute path, URL or symbolic link), and its `artifact_sha256` must match the file (as it
165
+ is, or with LF / CRLF line endings); otherwise code 3 with a reason that begins `integrity_ref:`. A `type: jos` child
166
+ sealed this way is verified before the parent's first step. Like `integrity.hash`, it detects change; it does not
167
+ authenticate anyone (whoever can edit the folder can rewrite both files). `jos validate` does not read the manifest.
168
+ - **It refuses what it cannot honor**, before the first step: an unmet `requires[]` entry, `security.tier: sandbox`
169
+ (this kernel cannot confine a process), an engine it does not have, a criterion it cannot evaluate: code 5.
170
+ - **A step sees only the environment it declares**: the names in `execution.env` (default `PATH`).
171
+ - **Every run is recorded**: a receipt per step and a hash-chained ledger that `jos ledger verify` recomputes.
172
+ - **`jos secrets`**: AES-256-GCM per entry with the name as associated data, a master key at mode 0600 in a 0700
173
+ folder, values never on a command line or stdout, `exec --only` for the one run that needs them.
174
+ - **`jos serve --mcp` is read-only by default**; `~/.jos/secrets` is denied to its file tools; writing and running
175
+ programs are separate opt-ins (`--allow-shell` is local code execution as you).
176
+ - **No network from `jos` or jos-core**, no telemetry. A step is a program you chose: it can use the network (the
177
+ provider examples call `curl`; `14-serve.jos` listens on 127.0.0.1) and it runs with your user's privileges; the
178
+ `shell` engine is not a sandbox, and its receipts say `isolation: "none"`.
179
+
180
+ `jos secrets` is a local, best-effort vault for a single user, not a hardened secrets manager. Provided "AS IS"
181
+ without warranty (Apache-2.0 §7–8); use at your own risk.
182
+
183
+ ### Disclaimer
184
+
185
+ Figures jos prints (cost, tokens, time) are estimates; vendor invoices are authoritative. Any AI output it relays can
186
+ be wrong: use the receipts and the ledger as evidence for your own decision. Provided "AS IS", without warranty
187
+ (Apache-2.0). The same text is the last line of `jos --help`.
188
+
189
+ ## Not yet
190
+
191
+ | What | Today | Planned |
192
+ |---|---|---|
193
+ | `engine: llm` and `judge` / `json_schema` criteria | valid documents, refused at run: code 5 | 2.1 |
194
+ | Signatures (`integrity.signature`, `security.tier: privileged`) | code 5 | 2.1 |
195
+ | Cache, crystals (`kind: crystal`), store | crystals validate; running one is code 5 | 2.1 |
196
+ | `jos get` (fetch a document from a registry) | frozen: exit 1 | 2.1, with signatures |
197
+ | Learning (`adaptive_ai` of 0.0.7 is kept as `x-legacy-adaptive_ai` and not read) | none | 2.1 |
198
+ | `jos serve` as an HTTP explorer | off: exit 2 (`jos serve --mcp` works) | F3 |
199
+ | `jos llm`, `jos prompts`, `jos repo`, `jos add` | frozen: exit 1, `jos <name>` says why | not scheduled |
200
+ | `josctl`, `@josfox/jos-cli` | separate older packages, not part of 5.0 | future |
201
+
202
+ ## Compatibility
203
+
204
+ | Input | What happens |
205
+ |---|---|
206
+ | `.jos` 2.0.0 | the format of this release |
207
+ | 1.1.0 short form (`{ "use": … }`) | normalized to a 2.0 atom with a `main` flow |
208
+ | 1.0.0 | normalized in memory to 2.0 (the file is not rewritten). Since jos-core 2.0.0-rc.8: `execution.flow` (`[{ invoke }]`) runs as the `main` flow, `execution.budget` bounds flows too, a gate keyed `after-<step>` is the gate of that step, and a 1.0.x document without `integrity.signature` may carry the 1.0.0 seal (hash computed with only the hash field empty) |
209
+ | 0.0.7 with text steps | normalized to a 2.0 flow and runs; `success_criteria` text is recorded, not evaluated |
210
+ | 0.0.7 sealed by `security.integrity_ref` | runs only if its manifest verifies (3 otherwise, also with `--dry-run`); `jos migrate` reseals it as a 2.0 document with `integrity.hash` |
211
+ | 0.0.7 written by `jos init` of 4.1.0 (with `orchestration.state`) | valid and runs since jos-core 2.0.0-rc.8: `orchestration.state` is kept as `orchestration.x-legacy-state` (never dropped), the `integrity_ref` manifest is verified first, `jos migrate` writes the 2.0 document (`read-0.0.7-init` in [PROMISES.md](PROMISES.md)) |
212
+ | 0.0.7 with object steps | invalid (2) |
213
+ | 0.0.7 with `type: prompt` steps (or any `type` other than `shell`, `jos`, `http`) | invalid (2) since jos-core 2.0.0-rc.8 (rc.3 to rc.7: valid and unexecutable, 5) |
214
+ | A step definition with a member the schema does not give it (`on_error` or `env` beside `run`, a `with` that is not an object) | invalid (2) since jos-core 2.0.0-rc.8: write `on_error` and `env` in the definition's `execution` |
215
+ | Integer values outside ±2^53 and below 10^21 however written (`9007199254740993`, `1e20`), decimals an IEEE 754 double does not keep exactly (`1.00000000000000001`, `1e-400`) | invalid (2) since jos-core 2.0.0-rc.8 (they were rounded, so two documents could share an `art_`, and `1e20` has no valid canonical form) |
216
+
217
+ `jos` reads 4.1.0's 13-key Minimal Valid Artifact unchanged: `jos validate` passes and `jos run` prints `Hello!`.
218
+
219
+ Every promise in one table, with what 4.1.0 did, what 5.0 does, what is planned and the command that proves it, known
220
+ gaps included: [PROMISES.md](PROMISES.md) (generated from `promises.json`; `npm test` runs every gate).
221
+
222
+ ## Changes since 4.1.0
223
+
224
+ - `run` and `validate` hand the document to jos-core. 4.1.0's own runner is gone: `execSync` of each step, the
225
+ 13-key presence check (only `jos` and `meta` are required now), `--insecure` and `--verbose` (both exit 64).
226
+ - `--flow <name>` exists again, with jos-core's meaning: it picks a flow of the document. A document with several
227
+ flows and none named `main` needs it.
228
+ - 0.0.7 flows with text steps run; object steps are invalid (2); `type: prompt` steps are invalid (2).
229
+ - Documents are validated with the published `schema/jos.schema.json` (jos-core 2.0.0-rc.8): integer values outside ±2^53
230
+ and below 10^21, and numbers not exactly representable, are invalid (2), as are members a definition does not have.
231
+ - `validate` shows what jos-core infers and what the document does not declare; `--strict` refuses (2) a document
232
+ without its own objective, success criteria and guardrails.
233
+ - `security.integrity_ref` is verified again (as in 4.1.0, stricter on paths), and `jos migrate` reseals it as
234
+ `integrity.hash`. A mismatch is code 3 (4.1.0: 1), `--dry-run` included; there is no bypass (`--insecure` is 64).
235
+ - Exit codes 0–5, 64, 70, 124, 137 (4.1.0 used 0 and 1).
236
+ - No more `~/.jos/runs/*/events.jsonl`: receipts in the result and jos-core's ledger replace it. `~/.jos/commands/`
237
+ overrides are not loaded; the command table is fixed.
238
+ - `init` writes a 2.0 atom (no `.sig.json`); `add`, `llm`, `prompts`, `get` and `repo` are frozen; `jos serve`
239
+ without `--mcp` is off.
240
+ - License Apache-2.0 (4.x: MIT). The `josfox` alias binary is no longer installed.
241
+
242
+ Full list: [CHANGELOG.md](CHANGELOG.md).
243
+
244
+ ## Use with an MCP client
245
+
246
+ `jos serve --mcp` is an MCP server over stdio: the client starts the command and talks JSON-RPC 2.0 on its stdin and
247
+ stdout. Started by hand in a terminal, it prints on stderr what it is, the root, the mode, its tools and how to
248
+ connect it, then waits (Ctrl-C to exit). `jos serve --help` prints the connection commands with the real paths
249
+ filled in; run it in the project folder you want to serve:
83
250
 
84
- ## The .jos Standard
251
+ ```
252
+ claude mcp add jos -- node /absolute/path/to/@josfox/jos/bin/jos serve --mcp --root /path/to/project
253
+ claude mcp remove jos
254
+ ```
85
255
 
86
- A `.jos` file binds **Intention** and **Execution** into one portable artifact:
256
+ For Claude Desktop (`claude_desktop_config.json`) or Cursor (`.cursor/mcp.json`):
87
257
 
88
258
  ```json
89
259
  {
90
- "meta": { "name": "example", "version": "1.0.0" },
91
- "intention": {
92
- "objective": "What this artifact does",
93
- "success_criteria": "How to know it worked"
94
- },
95
- "guardrails": { "avoid": [], "max_retries": 3 },
96
- "orchestration": {
97
- "flows": { "main": { "steps": ["task1", "task2"] } }
260
+ "mcpServers": {
261
+ "jos": {
262
+ "command": "node",
263
+ "args": ["/absolute/path/to/@josfox/jos/bin/jos", "serve", "--mcp", "--root", "/path/to/project"]
264
+ }
98
265
  }
99
266
  }
100
267
  ```
101
268
 
102
- > Full specification: [jos.md](./jos.md)
103
-
104
- ---
105
-
106
- ## Architecture
269
+ | Tool | What it is for |
270
+ |---|---|
271
+ | `list_files` | list the files and folders of a directory under the root (recursive to depth 3) |
272
+ | `read_file` | read a regular file under the root (at most 10 MB) |
273
+ | `search_files` | find files under the root by a glob on the name (`*` and `?`; 100 matches, depth 5) |
274
+ | `read_markdown` | read a Markdown file under the root, with its line count |
275
+ | `list_modules` | list the modules in `~/.jos/modules` and whether `~/.jos/modules.json` enables them |
276
+ | `list_processes` | list the runs recorded in `~/.jos/runs/*/report.json` |
277
+ | `hot_reload` | read `~/.jos/modules.json` again and report the enabled and disabled modules |
278
+ | `write_file` | only with `--allow-write`: create or overwrite a file under the root, never under `~/.jos` |
279
+ | `run_command` | only with `--allow-shell`: run a program with an argv (no shell) in a folder under the root; each one shown on stderr |
280
+
281
+ The server refuses to serve `/`, `$HOME` or a folder containing it unless `--root` names it. A client cannot reach a
282
+ file outside the root, cannot read, list, search or write `~/.jos/secrets` (nor any `.master` or `vault.json` under
283
+ `~/.jos`), cannot write without `--allow-write` and cannot run a program without `--allow-shell`.
284
+ **`--allow-shell` grants local code execution with your user privileges**: a program it starts can read your
285
+ secrets (`~/.jos/secrets`, `~/.ssh`, cloud keys) outside the MCP tools. Do not give it to an untrusted agent
286
+ without an external sandbox (a container or a virtual machine).
287
+
288
+ ## Frozen
289
+
290
+ Their code stays in `src/commands/_frozen/` and is neither published nor dispatched; each has a one-line
291
+ `*.README.md` with the reason, which is published (`jos <command>` prints its path).
292
+
293
+ | Frozen | Reason |
294
+ |---|---|
295
+ | `llm` | Overwrote the `.jos` with model output without validating it; sent the artifact with the encrypted vault file as the Bearer token. |
296
+ | `prompts` | `optimize` overwrote the `.jos` without validating it; the adapters fabricated quality scores. |
297
+ | `get` | Wrote to a path built from `meta.name` (path traversal); downloaded without verifying. |
298
+ | `repo` | Managed the repository list that only `get` used. |
299
+ | `serve` (HTTP file explorer) | Served files by working directory without a sandbox and exposed `~/.jos/secrets` when run from the home directory. Exit 2. |
300
+ | `add` | Edited 0.0.7 flows and regenerated their `.sig.json` manifest (which `jos run` verifies again; `jos migrate` replaces it with `integrity.hash`). Flows are written in the document now (`flow.jos` of `jos start`, `examples/03-flow-gate.jos`). |
301
+
302
+ ## Tests
107
303
 
108
304
  ```
109
- ~/.jos/
110
- ├── commands/ # Plugin architecture
111
- ├── artifacts/ # Package cache
112
- ├── secrets/ # Encrypted vault (AES-256)
113
- ├── runs/ # Execution epochs
114
- ├── repos.json # Repository configuration
115
- └── lock.json # Package integrity
305
+ npm test # the gate, in order; no network (Node 22 or later):
306
+ # jos verify; every test/*.test.js once with a per-file coverage gate (each published
307
+ # file of src/ and bin/jos >= 90 % lines, branches, functions; secrets.js and mcp.js 100 %);
308
+ # secrets and mcp alone at 100 %; nothing new left in HOME or in the repository
309
+ npm run test:host # host tests alone, without coverage (Node 18 or later)
310
+ npm run test:promises # the promise gate alone: every gate of promises.json (Node 18 or later)
311
+ npm run promises:md # regenerate PROMISES.md after editing promises.json
312
+ python3 -I bench/bench.py # host overhead over jos-core, appends to BENCHMARKS.md
116
313
  ```
117
314
 
118
- ---
119
-
120
- ## A2A Protocol Compatibility
121
-
122
- This implementation is designed to be compatible with agent interoperability protocols, including the A2A (Agent-to-Agent) Protocol.
123
-
124
- - **.jos artifacts** can serve as A2A task payloads
125
- - **Agents** can declare .jos compatibility in capability cards
126
- - **Integrity verification** ensures secure artifact exchange
315
+ `test/tour.test.js` runs the tour: every example validates in both kernels with the same `art_`; the ones without
316
+ keys run; the provider and Ollama examples run against a fake `curl` (and a fake `ollama`) on `PATH`, with and without
317
+ a key, and the key is checked absent from the output, the result, the receipts and the ledgers. The tests write only
318
+ to temporary folders; run them as `HOME=$(mktemp -d) npm test` on a machine where other `jos` processes write to
319
+ `~/.jos`.
127
320
 
128
- ---
321
+ `test/promises.test.js` runs every gate of `promises.json` in a temporary folder with a temporary HOME, no provider
322
+ key and a fake `curl` and `ollama` on `PATH`. A known gap asserts today's behaviour and prints `⚠ known gap: <id>`;
323
+ it fails the day the gap is fixed, and then the entry is updated. It also fails when a row of "What it can do" or
324
+ "Not yet" has no entry, or when `PROMISES.md` is stale.
129
325
 
130
- ## Disclaimer & Legal
326
+ ## Links
131
327
 
132
- ### AS-IS Software
133
-
134
- This software is provided **"AS IS"**, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfringement.
135
-
136
- ### Liability
137
-
138
- In no event shall the authors, contributors, or the JOS Open Standards Foundation be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software.
139
-
140
- ### Your Responsibility
141
-
142
- - You use this software **at your own risk**
143
- - You are responsible for validating its suitability for your use case
144
- - You are responsible for compliance with applicable laws and regulations
145
-
146
- ### Trademark Notice
147
-
148
- - **JOS**, **JOSFOX**, and the Kitsune fox logo are trademarks of the JOS Open Standards Foundation
149
- - **A2A Protocol** and related terminology are property of their respective owners
150
- - **Google** and related trademarks are property of Google LLC
151
- - All other trademarks are property of their respective owners
152
-
153
- This project is an independent open-source implementation and is **not affiliated with, endorsed by, or sponsored by** any third-party trademark holders mentioned in this documentation.
154
-
155
- ### Citation Requirement
156
-
157
- If you implement or distribute the .jos standard, you must include attribution to:
158
- - The **JOS Open Standards Foundation**
159
- - The official specification repository
160
-
161
- ---
328
+ Specification, schemas and conformance vectors: <https://github.com/josfox-jos/jos-spec>. Source:
329
+ <https://github.com/josfox-jos/jos>.
162
330
 
163
331
  ## License
164
332
 
165
- **MIT License** — See [LICENSE](./LICENSE) for details.
166
-
167
- Free to use for personal, educational, and commercial purposes.
168
-
169
- ---
170
-
171
- <div align="center">
172
-
173
- **Made with ❤️ & AI**
174
-
175
- *JOS Open Standards Foundation*
176
-
177
- ```
178
- ██╗ ██╗██╗████████╗███████╗██╗ ██╗███╗ ██╗███████╗
179
- ██║ ██╔╝██║╚══██╔══╝██╔════╝██║ ██║████╗ ██║██╔════╝
180
- █████╔╝ ██║ ██║ ███████╗██║ ██║██╔██╗ ██║█████╗
181
- ██╔═██╗ ██║ ██║ ╚════██║██║ ██║██║╚██╗██║██╔══╝
182
- ██║ ██╗██║ ██║ ███████║╚██████╔╝██║ ╚████║███████╗
183
- ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
184
- ```
185
-
186
- </div>
333
+ Apache License 2.0 ([LICENSE](LICENSE)), from version 5.0.0; versions up to 4.x were published under the MIT
334
+ license. Copyright JOSFOX LLC.
@@ -0,0 +1,117 @@
1
+ # `jos serve --mcp` — what an MCP client can and cannot do
2
+
3
+ Applies to `@josfox/jos` 5.0.0-rc.3 and later (verified again under 5.0.0) (closes H-01). Code: `src/mcp.js`, flag parsing in `src/serve.js`. Tests: `test/mcp.test.js` (gate: `npm run test:mcp`, 100 % lines, branches and functions of `src/mcp.js`).
4
+
5
+ `jos serve --mcp` speaks JSON-RPC 2.0 over stdio to one MCP client, usually an AI agent. Treat that client as untrusted: it acts on whatever text it has read, and a hostile web page, issue or file can steer it. The server is what stands between that client and the files of the user who started it.
6
+
7
+ ## The problem this fixes (H-01)
8
+
9
+ Up to 5.0.0-rc.2 the file tools accepted any path under the working directory **and under all of `~/.jos`**, compared lexically (no symlink resolution), `write_file` and `toggle_module` were always on, and `run_command` ran any command line through a shell (`execSync`) behind a list of five forbidden substrings. A client could read `~/.jos/secrets/.master` and `vault.json` directly, or through a symlink in the working directory, and decrypt every stored secret; or `cat` them through `run_command`.
10
+
11
+ ## Usage
12
+
13
+ ```
14
+ jos serve --mcp [--root <dir>] [--allow-write] [--allow-shell]
15
+ ```
16
+
17
+ | Flag | Default | Effect |
18
+ |---|---|---|
19
+ | (none) | read-only, root = working directory | `list_files`, `read_file`, `read_markdown`, `search_files`, `list_modules`, `hot_reload`, `list_processes` |
20
+ | `--root <dir>` | the working directory | the one directory the file tools may reach. Must exist (exit 64 otherwise, or when the value is missing) |
21
+ | `--allow-write` | off | adds `write_file` (inside the root, never under `~/.jos`) |
22
+ | `--allow-shell` | off | adds `run_command` (a program and an argv, no shell): **local code execution with your user privileges**, see [`--allow-shell`](#--allow-shell) |
23
+
24
+ **The server refuses to start (exit 2)** when the root would be `/`, your home directory, or any directory that contains your home directory, unless you named it yourself with `--root`. The home directory is the parent of the `~/.jos` the host uses.
25
+
26
+ A tool that is off is not in `tools/list`, and calling it is `-32601 Unknown tool`.
27
+
28
+ **Started by hand.** When stdin is a terminal (a person, not a client), the server writes a notice on **stderr**: that it is an MCP server waiting for a client, the root, the mode (read-only, or which of `--allow-write` / `--allow-shell` is on), that `~/.jos/secrets` and any `.master` or `vault.json` under `~/.jos` are denied, with `--allow-shell` the warning that the flag is local code execution with the user's privileges, the tools it offers with one line each, and how to connect it (`claude mcp add jos -- node <absolute path of this bin/jos> serve --mcp --root <root>`, `claude mcp remove jos`, and the JSON block for Claude Desktop / Cursor), then `Ctrl-C to exit.` Nothing of it goes to stdout, the JSON-RPC channel. When stdin is not a terminal, which is how a client starts the server, there is no notice. The examples it prints are read-only; `jos serve --help` prints the same, for the current directory or the `--root` given with `--help`, and warns when that root is `/`, `$HOME` or contains it.
29
+
30
+ ## Roots
31
+
32
+ | Tool | Reaches | Notes |
33
+ |---|---|---|
34
+ | `list_files`, `read_file`, `read_markdown`, `search_files` | the root, nothing else | `~/.jos` is **no longer a root**. If the root contains `~/.jos` (only possible with an explicit `--root`), the server says so on stderr at start; `~/.jos/secrets` stays denied |
35
+ | `write_file` (`--allow-write`) | the root, minus everything under `~/.jos` | |
36
+ | `list_modules`, `hot_reload` | read `~/.jos/modules.json`, `~/.jos/modules/*/package.json` | fixed names; the client chooses no path |
37
+ | `list_processes` | reads `~/.jos/runs/*/report.json` | fixed names; reports liveness with `kill(pid, 0)` |
38
+ | `toggle_module` | **removed** | it wrote `~/.jos/modules.json`; nothing writes under `~/.jos` any more |
39
+ | `run_command` (`--allow-shell`) | anything the user can do | see below |
40
+
41
+ ## How a path is checked
42
+
43
+ 1. The client's string must be a string without NUL; it is resolved against the root (`..`, `.`, repeated slashes collapse here).
44
+ 2. The result is turned into its **real path** with `fs.realpathSync.native` (which also returns the on-disk case on APFS). For a path that does not exist yet, the real path of its deepest existing ancestor is taken and the missing components are appended. A dangling symlink, a symlink loop, a component that is not a directory, or an unreadable component on the way makes the path **denied**.
45
+ 3. The real path must be the root or lie under it. A symlink inside the root that points outside it (for example to `~/.ssh`) is therefore denied.
46
+ 4. Neither the lexical nor the real path may be protected by name (next section); for a write, neither may lie under `~/.jos`.
47
+ 5. The **device and inode** of the path and of each of its existing ancestors are compared with those of `~/.jos/secrets` and of every entry in it (and, for a write, of `~/.jos`). This catches hard links, case or Unicode folding the name checks missed, and any other alias of the same directory.
48
+ 6. The file is opened **by that real path** with `O_NOFOLLOW` (and `O_NONBLOCK`, so a FIFO in the tree cannot hang the server); after opening, its inode is compared again. Writes open without `O_TRUNC`, check the inode, and only then truncate through the same descriptor, so a hard link to a secret is never emptied.
49
+
50
+ Listing and searching apply the same rules to every entry they meet: protected entries, hard links to a secret, and symlinks that leave the root, dangle or end in a protected place are left out (not reported as errors).
51
+
52
+ ## What is protected
53
+
54
+ - `~/.jos/secrets` and everything below it, in its lexical form, its real form and by inode (so a `~/.jos` that is itself a symlink, or a secrets directory reached by its real location inside the root, is still denied);
55
+ - any file named `.master` or `vault.json` anywhere under `~/.jos` (for example a stray copy);
56
+ - for writes, everything under `~/.jos`.
57
+
58
+ The comparison of names ignores case and Unicode normalization (APFS and NTFS are case-insensitive; on APFS even `ſ`, U+017F, is the same letter as `s`, which the real-path and inode steps catch). On a case-sensitive file system this also denies, for instance, `~/.jos/SECRETS`, which is a different directory there: over-denial, never under-denial.
59
+
60
+ The protected set is recomputed on every call, so a vault created while the server runs is protected from its first byte; a path under `~/.jos/secrets` is denied by name even before the directory exists, so a client cannot learn whether it does.
61
+
62
+ ## Errors
63
+
64
+ Out of the root and protected give the same message, `Access denied: path is outside the working directory or protected`, whether the file exists or not. Other errors name only the path the client wrote and an error code (`File not found: x`, `Cannot open x: EACCES`, `Not a regular file: x`). No error and no response carries the content of a denied file or a key; the tests check every response and stderr for the synthetic master key, ciphertext and secret value.
65
+
66
+ ## `--allow-shell`
67
+
68
+ **Warning: `--allow-shell` grants local code execution with the privileges of the user who runs `jos`.** A program started by `run_command` can read the secrets (`~/.jos/secrets`, `.master`, `~/.ssh`, cloud credentials) outside the MCP tools: the root, the path checks and the denials of this document bind the file tools, not that program. Do not give `--allow-shell` to an untrusted agent, or to a client that relays one, without an external sandbox (a container or a virtual machine that holds neither your home directory nor your keys). `jos serve --help` and the notice at start (stdin a terminal) print this warning, and the stderr warning at start of a server with the flag repeats it.
69
+
70
+ `jos serve --mcp --allow-shell` adds `run_command { command, args?, cwd?, timeout? }`:
71
+
72
+ - `command` is a program (looked up in `PATH`, or a path) and `args` an array of strings. It is started with `spawnSync(command, args, { shell: false })`: no command line, no expansion, no `;`, `|`, `$(...)` or globbing. `"echo hi; touch x"` as `command` is a program name that does not exist (`ENOENT`). A client that wants a shell has to ask for one explicitly (`command: "sh", args: ["-c", ...]`) — the flag allows that too; see below.
73
+ - `cwd` (default: the root) is checked like any path: inside the root, not protected; the root of the file system (`/`) is refused even when it is the root.
74
+ - At start the server writes a warning on stderr; **every command** is written to stderr before it runs (`▶ jos serve: run_command ["prog","arg"] in "<dir>"`, JSON-quoted so no terminal escape gets through).
75
+ - The child's environment is the server's minus every variable whose name (compared without case) is a secret name in `~/.jos/secrets/vault.json` or `~/.jos/secrets/keychain.json`. That also removes whatever `jos secrets exec --only A,B -- jos serve ...` put there. If either file exists but cannot be read or has another shape, the command is refused (better no command than one with a secret in its environment).
76
+ - stdin is `/dev/null` (never the protocol stream); timeout defaults to 30 s and is capped at 10 min; output is capped at 1 MiB.
77
+ - The old list of forbidden substrings (`rm -rf /`, `mkfs`, ...) is **gone**: it never was a defence (`rm -rf "/"` or `rm -fr /` passed it) and keeping it would suggest one.
78
+
79
+ **What `--allow-shell` does not protect: anything.** It runs any program as you. `cat ~/.jos/secrets/.master`, `sh -c ...`, reading `~/.ssh`, `~/.aws` or browser profiles, and `jos secrets exec` all work. No shell parsing only removes injection through a command line; stripping secret names from the environment only stops the secrets already in the server's environment from being handed over for free. Use the flag only with a client you would let type in your terminal.
80
+
81
+ ## What this does not protect
82
+
83
+ - **The root itself.** The client reads every file in it, dot files included when named (`read_file('.env')`; listings and searches skip dot files). With `--allow-write` it can overwrite any of them, including files that run code later: `.git/hooks/*`, `package.json` scripts, `Makefile`. Serve a project directory.
84
+ - **Races.** Steps 2–6 are not atomic. `O_NOFOLLOW` covers the last component and the inode is compared after opening; a process of the same user that swaps an intermediate directory for a symlink between the check and the open can still redirect the access. Such a process can read the secrets directly anyway.
85
+ - **Other users of the same uid**, root, and anything with a copy of `.master` (see SECURITY-SECRETS.md).
86
+ - **Secrets that are not in the vault**: environment variables with other names (`AWS_SECRET_ACCESS_KEY` set by your shell), credential files inside the root.
87
+ - **Copies** (not hard links) of a secret file outside `~/.jos`, or under `~/.jos` with a name other than `.master` or `vault.json`, for example a backup someone left in the root. A hard link to the original is denied by inode wherever it is.
88
+ - **Windows**: the code has no platform branch and drops the flags Windows lacks, but it has not been run there.
89
+
90
+ ## Tests and coverage
91
+
92
+ `test/mcp.test.js`, 40 tests, in-process (calling `handleMessage` and `startMCPServer` with streams) and over the real stdio protocol (`bin/jos serve --mcp`), each in its own temporary `HOME` with a synthetic vault (one of them written by the real `jos secrets set --stdin`). `JOS_SECRETS_BACKEND` and `JOS_TEST_KEYCHAIN` are removed from the test process and from every child.
93
+
94
+ - M1 every spelling of a path into the secrets (relative, absolute, `..`, `./`, case, `ſ` on macOS, repeated slashes, the directory itself, missing files under it, before the directory exists, a symlinked `~/.jos`, a root inside the secrets) is denied for all five file tools; out-of-root paths get the same message whether they exist or not;
95
+ - M2 a symlink and a hard link in the root to `.master` are denied and hidden; symlinks out of the root, dangling, looping or through a file are denied, a symlink inside the root works;
96
+ - M3 `run_command` is absent and unknown without `--allow-shell`; with it, it runs an argv without a shell (metacharacters stay bytes), is announced on stderr, and `env` in the child shows none of the vault and keychain names (also in other case); `cwd` outside the root or `/` is refused; refusal when the names cannot be read; failure, signal, timeout and bad `cwd` are reported;
97
+ - M4 reads, overwrites, listings, recursive listings and glob searches inside the root keep working; FIFO, `EACCES`, size limit and invalid parameters give clean errors;
98
+ - M6 read-only by default (`write_file` and the removed `toggle_module` are unknown tools); with `--allow-write` nothing is written under `~/.jos`, from a root that contains it or from a root inside it;
99
+ - M7 `/`, `$HOME` and an ancestor of it are refused without `--root` (exit 2), accepted with it; `--root` without a value or to a missing directory exits 64;
100
+ - the notice: with stdin a terminal it is on stderr and stdout stays empty, it lists only the tools that are on and carries an existing absolute path of `bin/jos` and the root in its examples; without a terminal there is none; `--help` lists every tool with its flag, has no `undefined`, and its examples carry real paths (and the short `jos` form only when `jos` on `PATH` is this `bin/jos`); `--help`, and the notice when `--allow-shell` is on (only then), carry the warning that the flag is local code execution and needs an external sandbox for an untrusted agent;
101
+ - module tools: `modules.json` as a symlink or a hard link into the secrets reads as missing; `~/.jos/runs` or `~/.jos/modules` as a link to `~/.jos/secrets` lists nothing and reads nothing from it, with a file planted one level inside the secrets that only the `homePath` check (real path, then name denial) stops, since the inode comparison after opening covers only the direct entries of the secrets directory (R-18);
102
+ - a symlink swapped in for the last component between the path check and the open (the test wraps `fs.openSync`, the call that opens the file) is refused for `read_file` and for `write_file`, with a decoy inside the root that is not a secret, so only `O_NOFOLLOW` can stop it (R-17).
103
+
104
+ Coverage of `src/mcp.js`: 100 % lines, branches and functions, without exclusions. Two tests are skipped when run as root (`EACCES` cannot be produced) and three on Windows (FIFO, and the two `O_NOFOLLOW` tests, which say so: the flag does not exist there); in those environments the gate cannot reach 100 % branches.
105
+
106
+ Sensitivity, checked by mutation on a scratch copy (each mutant must make `npm run test:mcp` fail): removing the path jail, the realpath step, the root check, the name denial, the ancestor inode check, the inode check at open, the `~/.jos` write block, the write flag, the `--allow-write` or `--allow-shell` gate, `shell: false`, the environment filter, `O_NONBLOCK` (the FIFO test then hangs and is killed), `O_NOFOLLOW` (the two swap tests then read or write through the symlink and fail), the realpath or the protection in `homePath` (the two `~/.jos/runs`/`~/.jos/modules` link tests then list the planted entry), the listing filter, the refusal to serve `$HOME` or the `cwd: "/"` check — every one fails the gate.
107
+
108
+ ## Incompatible changes
109
+
110
+ - Read-only by default: `write_file` exists only with `--allow-write`, and never writes under `~/.jos`. `toggle_module` is removed.
111
+ - `run_command` exists only with `--allow-shell`, and takes a program plus an `args` array; a command line in `command` is no longer run through a shell.
112
+ - `jos serve --mcp` refuses to start in `/`, in `$HOME` or in a directory containing it, unless `--root` names the directory.
113
+ - The file tools no longer reach `~/.jos` from a root outside it.
114
+ - A symlink in the root that points outside it is no longer followed (denied, and hidden from listings).
115
+ - `search_files` takes a glob on the file name (`*`, `?`, anchored at both ends), no longer a regular expression made from the pattern; `hello` no longer matches `hello.txt`. The 100-match limit now holds within one directory too.
116
+ - `read_file` refuses unknown encodings and non-regular files; `write_file` refuses non-string content.
117
+ - Error texts for denied paths changed to the single message above.