knosky 0.6.3 → 0.7.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 (64) hide show
  1. package/CHANGELOG.md +149 -93
  2. package/CREDITS.md +14 -14
  3. package/LICENSE.md +76 -76
  4. package/LIMITATIONS.md +33 -23
  5. package/PRIVACY.md +30 -30
  6. package/README.md +170 -117
  7. package/SECURITY.md +78 -46
  8. package/action/post-comment.mjs +94 -89
  9. package/action.yml +62 -62
  10. package/bin/knosky.mjs +279 -105
  11. package/core/CONTRACT.md +70 -70
  12. package/core/append-only-checkpoint.mjs +215 -0
  13. package/core/audit-writer.mjs +317 -0
  14. package/core/benchmark-results.mjs +225 -225
  15. package/core/bundle.mjs +178 -178
  16. package/core/churn.mjs +23 -23
  17. package/core/ci.mjs +268 -268
  18. package/core/comparison.mjs +189 -189
  19. package/core/config.mjs +189 -189
  20. package/core/constants.mjs +13 -13
  21. package/core/contract.mjs +123 -123
  22. package/core/cross-repo.mjs +111 -111
  23. package/core/decision-codes.mjs +92 -0
  24. package/core/destination.mjs +161 -161
  25. package/core/district-classification.mjs +111 -0
  26. package/core/doctor-scorecard.mjs +369 -0
  27. package/core/domain-store.mjs +347 -0
  28. package/core/edges.mjs +43 -43
  29. package/core/escalate.mjs +68 -68
  30. package/core/freshness.mjs +198 -194
  31. package/core/fs-indexer.mjs +218 -218
  32. package/core/key-store.mjs +348 -348
  33. package/core/layout.mjs +46 -46
  34. package/core/ledger.mjs +176 -141
  35. package/core/local-ipc-identity.mjs +500 -0
  36. package/core/lod.mjs +155 -155
  37. package/core/mode-b.mjs +410 -0
  38. package/core/multi-model-benchmark.mjs +405 -405
  39. package/core/net-lockdown.mjs +421 -0
  40. package/core/onboarding.mjs +223 -223
  41. package/core/operator-auth.mjs +317 -0
  42. package/core/overlays.mjs +45 -45
  43. package/core/policy-lattice.mjs +142 -0
  44. package/core/pr-comment.mjs +198 -198
  45. package/core/protocol-spec.mjs +460 -460
  46. package/core/provenance.mjs +320 -0
  47. package/core/retrieve.mjs +63 -63
  48. package/core/route.mjs +304 -304
  49. package/core/schema.mjs +275 -275
  50. package/core/signing-tiers.mjs +1265 -0
  51. package/core/swarm-bench.mjs +106 -0
  52. package/core/swarm-coordinator.mjs +867 -0
  53. package/core/trust-root-rekey.mjs +410 -0
  54. package/mcp/server.mjs +264 -108
  55. package/package.json +56 -46
  56. package/renderer/art/kenney/buildingTiles_sheet.xml +130 -130
  57. package/renderer/art/kenney/cityDetails_sheet.xml +12 -12
  58. package/renderer/art/kenney/landscapeTiles_sheet.xml +129 -129
  59. package/renderer/art/kenney/sheet_allCars.xml +545 -545
  60. package/renderer/build-rich.mjs +43 -43
  61. package/renderer/city.template.html +808 -808
  62. package/ssot/decision-codes.json +133 -0
  63. package/ssot/ladder-l0-l3.md +232 -0
  64. package/ssot/tool-menu.json +130 -0
package/README.md CHANGED
@@ -1,117 +1,170 @@
1
- # KnoSky
2
-
3
- *Your knowledge as a skyline.*
4
-
5
- **Turn any repo or folder of docs into a living, explorable city — and give your AI assistant grounded, cited answers from your own source. Runs entirely on your machine. Free.**
6
-
7
- > *Born from building [Sathia](https://sathia.ai).*
8
-
9
- ---
10
-
11
- ## See it in 10 seconds
12
-
13
- Open **[`demo/knosky-demo.html`](./demo/knosky-demo.html)** in your browser (just double-click it — it's a single self-contained file). That's a sample project rendered as a city. Now point it at your own.
14
-
15
- ---
16
-
17
- ## The problem
18
- Your project grows faster than anyone can hold in their head. Hundreds of files, decisions, and docs across folders. A file tree tells you what files exist — not what the *system* is. So things get lost, decisions get re-litigated, and your AI assistant confidently makes things up about your own codebase.
19
-
20
- ## What you get (the outcome)
21
-
22
- > **Measured (SAT-439, 5 tasks, naive agent vs. KnoSky-guided agent):**
23
- > **68% fewer tokens · 70% fewer tool calls · 6× faster to the right file**
24
- > *(All guided runs answered correctly; the naive agent found the target in 3 of 5 tasks and answered correctly in 0.)*
25
-
26
- - **See your whole project in one screen.** Instead of scrolling a file tree, you see the *shape* of everything — which areas are big, how they connect, where the gaps are. New collaborators get oriented in minutes, not weeks.
27
- - **Find anything in seconds.** Search the city or ask your assistant *"where does auth live / what did we decide about billing"* — and jump straight to the **live file**.
28
- - **Your AI answers from YOUR source, with citations.** Connect it to Claude / Cursor / VS Code / Gemini and your assistant stops guessing about your codebase — it cites the real file, every time.
29
- - **Zero setup tax, local-first by default.** Point it at a folder a city in under a minute. Deterministic, **$0 tokens** to keep fresh, **nothing ever leaves your machine**, and a fail-closed secret scan before you share. (KnoSky does not claim "zero data risk" — see [PRIVACY.md](./PRIVACY.md).)
30
-
31
- **Net:** faster comprehension, reliable recall of your own knowledge, and a grounded AIwithout giving up privacy or paying a cent.
32
-
33
- ## Who it's for
34
- Developers, founders, and architects sitting on a sprawling repo or knowledge base who want to *understand and navigate their own work fast* and want their AI assistant grounded in it — without uploading anything.
35
-
36
- ---
37
-
38
- ## How it works
39
-
40
- **Quickstart — one command:**
41
- ```bash
42
- npx knosky .
43
- ```
44
- Indexes the current folder, opens the city, prints the MCP config for your AI assistant (Claude Code / Claude Desktop / Cursor / VS Code) plus a few starter prompts, and starts the local connector. Point it anywhere with `npx knosky /path/to/your/repo`. Flags: `--no-open`, `--no-serve`.
45
-
46
- Prefer a clone? `git clone https://github.com/SathiaAI/knosky && cd knosky && npm install && node bin/knosky.mjs .`
47
-
48
- Want the individual pieces instead? Read on.
49
-
50
- **Requirements:** [Node.js](https://nodejs.org) 18+.
51
-
52
- **1. Build your city from a folder or repo**
53
- ```bash
54
- node core/fs-indexer.mjs --root /path/to/your/repo --out city-data.json --share-safe
55
- node renderer/build-rich.mjs city-data.json city.html
56
- ```
57
- Open `city.html`. Every top-level folder is a **district**, every file a **building**, every building links to the real source.
58
-
59
- **Flags:** `--share-safe` strips your absolute path (basename only) and prints a safety report — and the build **fails closed** if a secret-like value is detected. `--redact AcmeCorp,SecretProject` masks (and skips files matching) project-specific terms. `--include-absolute-root` keeps the full local path (private diagnostics only). `--allow-leaks` overrides the secret block (not recommended).
60
-
61
- **2. Connect it to your AI assistant (MCP)**
62
- ```bash
63
- cd mcp && npm install && cd ..
64
- # Claude Code:
65
- claude mcp add knosky -e KC_CITY=/abs/path/city-data.json -- node /abs/path/mcp/server.mjs
66
- ```
67
- Or add to your Claude Desktop / Cursor / VS Code MCP config:
68
- ```json
69
- "knosky": {
70
- "command": "node",
71
- "args": ["/abs/path/mcp/server.mjs"],
72
- "env": { "KC_CITY": "/abs/path/city-data.json" }
73
- }
74
- ```
75
- Then ask: *"search KnoSky for what we decided about authentication."* Read-only tools exposed: `kc_search`, `kc_get_node`, `kc_list_categories`, `kc_get_provenance`, `kc_related`.
76
-
77
- **See how your code connects.** Select a file in the city to see its **connections** (roads to the files it imports / that import it) and **churn** (recently-changed files glow). Or ask your assistant *"what connects to src/auth.js?"* — file-level structure only, not code analysis.
78
-
79
- **3. Get PR navigation comments automatically (GitHub Action — PR-GPS)**
80
-
81
- Add this to any workflow that runs on pull requests:
82
-
83
- ```yaml
84
- - name: Check out the repo (full history so the diff works)
85
- uses: actions/checkout@v4
86
- with:
87
- fetch-depth: 0
88
-
89
- - name: KnoSky PR-GPS
90
- uses: SathiaAI/knosky@v0.5.0
91
- with:
92
- base: ${{ github.event.pull_request.base.sha }}
93
- head: ${{ github.event.pull_request.head.sha }}
94
- ```
95
-
96
- The action posts (and updates) a single advisory comment on the PR listing which files changed, suggested review starting points, and related tests/docs. It reads file/folder/import structure only — never uploads code bodies — and **never blocks or gates the build**. The `github-token` input defaults to the workflow token; a `fail-on-secret` guard is available for stricter CI setups (see [`action.yml`](./action.yml) for all inputs).
97
-
98
- ---
99
-
100
- ## What it is **not** (on purpose)
101
- - **Not a code-RAG engine.** It won't read your whole codebase and answer deep questions. It's a **map and a router** — it tells you *where* things are and hands you (or your AI) the source. (Reach for Cursor/Glean for deep code Q&A.)
102
- - **Not a cloud service.** No upload, no account, nothing leaves your machine. (A hosted option may come later.)
103
- - **Not a copy of your knowledge.** A navigable **index of pointers** — titles, headings, short excerpts that link back to the live file — never your full file contents.
104
-
105
- ## Privacy & safety
106
- - Runs locally; your source never leaves your machine.
107
- - Indexes pointers + light projections (title, headings, ~200-char excerpt, tags) — **never full file bodies**.
108
- - Skips `.git`, `node_modules`, `secrets/`, `keys/`, `.env*`, plus your `.gitignore`/`.kcignore` (git's own ignore rules apply inside a repo).
109
- - Scrubs common secret/PII patterns and **fails the build closed** if a secret-like value is detected. Build with **`--share-safe`** for a safety report before sharing.
110
- - Generated cities embed your data as inert JSON and escape untrusted file/folder names — opening or sharing a city won't execute injected code.
111
-
112
- More: **[PRIVACY.md](./PRIVACY.md)** · **[LIMITATIONS.md](./LIMITATIONS.md)** · **[SECURITY.md](./SECURITY.md)** · **[CHANGELOG.md](./CHANGELOG.md)** · **[How KnoSky compares](./wiki/comparison.md)**
113
-
114
- ## License & credits
115
- Free to use under the **[Functional Source License (FSL-1.1-MIT)](./LICENSE.md)** use it freely; you just can't repackage it as a competing product. Converts to MIT two years after each release. "KnoSky" is a trademark of the author.
116
-
117
- City artwork is **[Kenney](https://kenney.nl)** (CC0 / public domain) see [CREDITS.md](./CREDITS.md).
1
+ # KnoSky
2
+
3
+ *Your knowledge as a skyline.*
4
+
5
+ **Turn any repo or folder of docs into a living, explorable city — and give your AI assistant grounded, cited answers from your own source. Runs entirely on your machine. Free.**
6
+
7
+ > *Born from building [Sathia](https://sathia.ai).*
8
+
9
+ ---
10
+
11
+
12
+ > **Trust model (approved wording):** KnoSky's local trust model applies the core security principles of TUF — role separation, threshold signing, survivable key compromise, and freshness-guaranteed revocation — adapted from TUF's server-oriented update distribution to a fully local, no-egress agentic environment, with attestation formats based on in-toto/DSSE.
13
+
14
+ ## See it in 10 seconds
15
+
16
+ Open **[`demo/knosky-demo.html`](./demo/knosky-demo.html)** in your browser (just double-click it — it's a single self-contained file). That's a sample project rendered as a city. Now point it at your own.
17
+
18
+ ---
19
+
20
+ ## The problem
21
+ Your project grows faster than anyone can hold in their head. Hundreds of files, decisions, and docs across folders. A file tree tells you what files exist — not what the *system* is. So things get lost, decisions get re-litigated, and your AI assistant confidently makes things up about your own codebase.
22
+
23
+ ## What you get (the outcome)
24
+
25
+ > **Measured (SAT-439, 5 tasks, naive agent vs. KnoSky-guided agent):**
26
+ > **68% fewer tokens · 70% fewer tool calls · faster to the right file**
27
+ > *(All guided runs answered correctly; the naive agent found the target in 3 of 5 tasks and answered correctly in 0.)*
28
+
29
+ - **See your whole project in one screen.** Instead of scrolling a file tree, you see the *shape* of everything which areas are big, how they connect, where the gaps are. New collaborators get oriented in minutes, not weeks.
30
+ - **Find anything in seconds.** Search the city — or ask your assistant *"where does auth live / what did we decide about billing"* — and jump straight to the **live file**.
31
+ - **Your AI answers from YOUR source, with citations.** Connect it to Claude / Cursor / VS Code / Gemini and your assistant stops guessing about your codebase it cites the real file, every time.
32
+ - **Zero setup tax, local-first by default.** Point it at a folder → a city in under a minute. Deterministic, **$0 tokens** to keep fresh, **nothing ever leaves your machine**, and a fail-closed secret scan before you share. (KnoSky does not claim "zero data risk" — see [PRIVACY.md](./PRIVACY.md).)
33
+
34
+ **Net:** faster comprehension, reliable recall of your own knowledge, and a grounded AIwithout giving up privacy or paying a cent.
35
+
36
+ ## Who it's for
37
+ Developers, founders, and architects sitting on a sprawling repo or knowledge base who want to *understand and navigate their own work fast* — and want their AI assistant grounded in it — without uploading anything.
38
+
39
+ ---
40
+
41
+ ## How it works
42
+
43
+ **Quickstart — one command:**
44
+ ```bash
45
+ npx knosky .
46
+ ```
47
+ Indexes the current folder, opens the city, prints the MCP config for your AI assistant (Claude Code / Claude Desktop / Cursor / VS Code) plus a few starter prompts, and starts the local connector. Point it anywhere with `npx knosky /path/to/your/repo`. Flags: `--no-open`, `--no-serve`.
48
+
49
+ Prefer a clone? `git clone https://github.com/SathiaAI/knosky && cd knosky && npm install && node bin/knosky.mjs .`
50
+
51
+ Want the individual pieces instead? Read on.
52
+
53
+ **Requirements:** [Node.js](https://nodejs.org) 18+.
54
+
55
+ **1. Build your city from a folder or repo**
56
+ ```bash
57
+ node core/fs-indexer.mjs --root /path/to/your/repo --out city-data.json --share-safe
58
+ node renderer/build-rich.mjs city-data.json city.html
59
+ ```
60
+ Open `city.html`. Every top-level folder is a **district**, every file a **building**, every building links to the real source.
61
+
62
+ **Flags:** `--share-safe` strips your absolute path (basename only) and prints a safety report — and the build **fails closed** if a secret-like value is detected. `--redact AcmeCorp,SecretProject` masks (and skips files matching) project-specific terms. `--include-absolute-root` keeps the full local path (private diagnostics only). `--allow-leaks` overrides the secret block (not recommended).
63
+
64
+ **2. Connect it to your AI assistant (MCP)** — DEC-108 menu
65
+
66
+ SSOT: [`ssot/tool-menu.json`](./ssot/tool-menu.json) · decision codes: [`ssot/decision-codes.json`](./ssot/decision-codes.json) · ladder: [`ssot/ladder-l0-l3.md`](./ssot/ladder-l0-l3.md). README = wiki = binary for tool names (AR-03).
67
+
68
+ ```bash
69
+ cd mcp && npm install && cd ..
70
+ # Claude Code (coding profile — default packs):
71
+ claude mcp add knosky -e KC_CITY=/abs/path/city-data.json -e KC_PROFILE=coding -- node /abs/path/mcp/server.mjs
72
+ ```
73
+ Or add to your Claude Desktop / Cursor / VS Code / Codex MCP config:
74
+ ```json
75
+ "knosky": {
76
+ "command": "node",
77
+ "args": ["/abs/path/mcp/server.mjs"],
78
+ "env": {
79
+ "KC_CITY": "/abs/path/city-data.json",
80
+ "KC_PROFILE": "coding"
81
+ }
82
+ }
83
+ ```
84
+
85
+ | Profile (`KC_PROFILE`) | What it is for | Tools |
86
+ | :--- | :--- | :--- |
87
+ | **coding** (default packs) | Hermes, Claude Code, Cursor, Codex CLI, VS Code | Tier 0 map + Tier 1 governed |
88
+ | **security** | Operators / CI verify | Coding tools + `kc_audit_query`, `kc_audit_verify` |
89
+ | **advisory** | Explicit non-authorizing explore | Tier 0 map (+ labeled Mode A `kc_route`) |
90
+
91
+ **Mode A vs Mode B (DEC-106 / DEC-108)**
92
+
93
+ | Mode | Label on the wire | Meaning |
94
+ | :--- | :--- | :--- |
95
+ | **Mode A** | `ADVISORY_UNAUTH` | Non-authorizing map / tips. Not policy-certified, not swarm-safe. |
96
+ | **Mode B** | `ALLOW` or `DENY_*` (+ receipt on ALLOW) | Governed: identity + policy + audit before any authorized claim. |
97
+
98
+ Default coding profile uses **Mode B** for governed tools. Mode A is available via `KC_PROFILE=advisory` or `advisory=true` on dual-mode `kc_route` — always labeled; never market it as governed.
99
+
100
+ **Official tool menu (freeze)**
101
+
102
+ | Tier | Tools | Authorizing? |
103
+ | :--- | :--- | :---: |
104
+ | **Tier 0 map** | `kc_search`, `kc_get_node`, `kc_list_categories`, `kc_get_provenance`, `kc_related` | No (map only) |
105
+ | **Tier 1 governed** | `kc_route` (**dual-mode**), `kc_bundle`, `kc_policy_check` | Yes when Mode B `ALLOW` |
106
+ | **Security profile only** | `kc_audit_query`, `kc_audit_verify` | Audit verify — not on coding packs |
107
+
108
+ - **`kc_route`** GPS toward a destination. Mode B: `ALLOW` / `DENY_*` with audit receipt; Mode A: labeled `ADVISORY_UNAUTH` tips only.
109
+ - **`kc_bundle`** Share-oriented pointer / intent-manifest under the same gates + fail-closed secret scan.
110
+ - **`kc_policy_check`** Dry-run policy decision + reasons without preferring a full route body.
111
+
112
+ **Closed decision codes** (no open strings on the wire):
113
+ `ALLOW` · `DENY` · `DENY_IDENTITY` · `DENY_POLICY` · `DENY_AUDIT` · `DENY_FRESHNESS` · `DENY_EVIDENCE` · `ERROR_INVALID_INPUT` · `ERROR_INDEX` · `ADVISORY_UNAUTH`
114
+
115
+ Then ask: *"search KnoSky for what we decided about authentication"* or *"route me to the auth policy under Mode B."*
116
+
117
+ **Guarantee ladder (honest claims — DEC-110)** never claim higher than `knosky doctor` for this install:
118
+
119
+ | L | Name | Promise |
120
+ | :---: | :--- | :--- |
121
+ | **L0** | Local map | Index + navigate locally; no KnoSky upload by default |
122
+ | **L1** | Share-safe | Fail-closed secret controls before share artifacts |
123
+ | **L2** | Governed evaluator | Mode B identity + policy + audit (or safe DENY) |
124
+ | **L3** | Swarm domain | Multi-agent coordinator on Mode B (leases/quotas/…) |
125
+
126
+ Wave 1 ships **L0–L2 paths** and an **L3 coordinator foundation** (local modules + `doctor` claim ceiling only — not “L3 ready at every install”). Do **not** treat marketing copy as “production swarm-safe at every install” until `doctor` reports `l3_ready` for that domain. Windows kernel lockdown is **unsupported/inactive** until packaging proves otherwise — see [LIMITATIONS.md](./LIMITATIONS.md).
127
+
128
+ **Packs (Wave 1)** — same DEC-108 menu: P0 Hermes · Claude Code · Cursor · Codex CLI · P1 VS Code MCP · Greptile recipe · GHA PR-GPS. Audit tools stay off coding packs.
129
+
130
+ **See how your code connects.** Select a file in the city to see its **connections** (roads to the files it imports / that import it) and **churn** (recently-changed files glow). Or ask your assistant *"what connects to src/auth.js?"* — file-level structure only, not code analysis.
131
+
132
+ **3. Get PR navigation comments automatically (GitHub Action — PR-GPS)**
133
+
134
+ Add this to any workflow that runs on pull requests:
135
+
136
+ ```yaml
137
+ - name: Check out the repo (full history so the diff works)
138
+ uses: actions/checkout@v4
139
+ with:
140
+ fetch-depth: 0
141
+
142
+ - name: KnoSky PR-GPS
143
+ uses: SathiaAI/knosky@v0.5.0
144
+ with:
145
+ base: ${{ github.event.pull_request.base.sha }}
146
+ head: ${{ github.event.pull_request.head.sha }}
147
+ ```
148
+
149
+ The action posts (and updates) a single advisory comment on the PR listing which files changed, suggested review starting points, and related tests/docs. It reads file/folder/import structure only — never uploads code bodies — and **never blocks or gates the build**. The `github-token` input defaults to the workflow token; a `fail-on-secret` guard is available for stricter CI setups (see [`action.yml`](./action.yml) for all inputs).
150
+
151
+ ---
152
+
153
+ ## What it is **not** (on purpose)
154
+ - **Not a code-RAG engine.** It won't read your whole codebase and answer deep questions. It's a **map and a router** — it tells you *where* things are and hands you (or your AI) the source. (Reach for Cursor/Glean for deep code Q&A.)
155
+ - **Not a cloud service.** No upload, no account, nothing leaves your machine. (A hosted option may come later.)
156
+ - **Not a copy of your knowledge.** A navigable **index of pointers** — titles, headings, short excerpts that link back to the live file — never your full file contents.
157
+
158
+ ## Privacy & safety
159
+ - Runs locally; your source never leaves your machine.
160
+ - Indexes pointers + light projections (title, headings, ~200-char excerpt, tags) — **never full file bodies**.
161
+ - Skips `.git`, `node_modules`, `secrets/`, `keys/`, `.env*`, plus your `.gitignore`/`.kcignore` (git's own ignore rules apply inside a repo).
162
+ - Scrubs common secret/PII patterns and **fails the build closed** if a secret-like value is detected. Build with **`--share-safe`** for a safety report before sharing.
163
+ - Generated cities embed your data as inert JSON and escape untrusted file/folder names — opening or sharing a city won't execute injected code.
164
+
165
+ More: **[PRIVACY.md](./PRIVACY.md)** · **[LIMITATIONS.md](./LIMITATIONS.md)** · **[SECURITY.md](./SECURITY.md)** · **[CHANGELOG.md](./CHANGELOG.md)** · **[How KnoSky compares](./wiki/comparison.md)**
166
+
167
+ ## License & credits
168
+ Free to use under the **[Functional Source License (FSL-1.1-MIT)](./LICENSE.md)** — use it freely; you just can't repackage it as a competing product. Converts to MIT two years after each release. "KnoSky" is a trademark of the author.
169
+
170
+ City artwork is **[Kenney](https://kenney.nl)** (CC0 / public domain) — see [CREDITS.md](./CREDITS.md).
package/SECURITY.md CHANGED
@@ -1,46 +1,78 @@
1
- # Security Policy
2
-
3
- KnoSky is a **local-first** tool: it runs on your machine, indexes your own folder, and writes a self-contained city file. It does not upload your source and has no hosted backend. The security surface that matters is the **generated artifact** (the city HTML / `city-data.json`) and the local indexer/MCP.
4
-
5
- ## Supported versions
6
-
7
- The latest release on the `main` branch is supported. KnoSky is pre-1.0; please run the newest version before reporting an issue.
8
-
9
- ## Reporting a vulnerability
10
-
11
- Please report security issues **privately** — do not open a public issue for anything exploitable.
12
-
13
- - Preferred: GitHub **"Report a vulnerability"** (Security → Advisories) on this repository, which opens a private channel.
14
- - We aim to acknowledge reports within **5 business days** and to publish a fix or mitigation for confirmed issues as quickly as is practical.
15
-
16
- When reporting, please include: the version/commit, steps to reproduce, and the impact you observed. A minimal proof-of-concept (e.g., a tiny repo or `city-data.json` that triggers the issue) helps a lot.
17
-
18
- ## Safe testing guidelines
19
-
20
- - Test only against repositories and folders **you own or control**.
21
- - Do not include real secrets or third-party personal data in proof-of-concept artifacts.
22
- - Do not perform testing that degrades the service for others (there is no shared service; this is a local tool).
23
-
24
- ## Handling generated artifacts (for users)
25
-
26
- - A generated city embeds **file names, paths, headings, and short excerpts** — not full file bodies. Treat a generated file as you would a directory listing with snippets.
27
- - Build with `--share-safe` before sharing: it strips the absolute root path (basename only), runs a fail-closed secret scan, and prints a safety report. Builds **fail closed** if a secret-like value is detected (override only with `--allow-leaks`).
28
- - Open city files you did not generate the same way you would any untrusted HTML.
29
-
30
- ## Trust model
31
-
32
- KnoSky's local trust model applies the core security principles of TUF — role separation, threshold signing, survivable key compromise, and freshness-guaranteed revocation — adapted from TUF's server-oriented update distribution to a fully local, no-egress agentic environment, with attestation formats based on in-toto/DSSE.
33
-
34
- ## Known boundary conditions
35
-
36
- **N=2 key-store quorum (D-163):** when a key store holds exactly 2 non-revoked keys, the revocation quorum formula (`Math.floor(peers/2)+1`) reduces to 1, meaning the sole peer key alone can revoke the other. This is mathematically unavoidable at N=2, is an accepted limitation (not a defect), and is explicitly exercised by red-team scenario RT-KS-001 (`test/key-store-quorum-redteam.mjs`). Deployments that require real threshold protection must maintain N≥3 non-revoked keys at all times.
37
-
38
- **M=0 sole-key self-revocation (D-167):** when a key store has been reduced to exactly 1 non-revoked key (via prior legitimate revocations), `revokeKey()` allows that key to be revoked with zero approvals (`required = 0`). This is an intentional design choice — a *self-wipe*, not a third-party bypass. Requiring peer approval when no peers exist would make a compromised last key permanently irrevocable, which is a worse outcome. D-167's quorum protection is meaningful only at N≥3; this is the N=1 edge of the same quorum curve. **Caller-identity note:** `revokeKey()` does not currently authenticate the caller as the holder of the key being revoked — it authorizes the *state* (M=0), not the *caller*. `revokeKey` has no production call sites today (enforced by an automated whole-repo scan, `test/key-store-quorum-redteam.mjs`), so this is not a live exploitable path. **This is not a simple fix:** each key's raw material (`KeyEntry.raw`) lives inside the same `ks` object `revokeKey` receives, so any caller with access to `ks` already has direct read access to every key's raw material — a self-signature check bolted onto `revokeKey` today would be trivially satisfiable by any caller (they can just read `raw` and compute the expected signature themselves), providing no real security. A genuine fix requires an architectural change — key material stored somewhere the `ks`-holding caller cannot directly read (e.g. an OS keychain or a separate process boundary) — not a signature parameter added to the existing function. Tracked and scoped as such: SAT-478. Explicitly exercised by red-team scenario RT-KS-002 (`test/key-store-quorum-redteam.mjs`). Cross-reference: SAT-472 (quorum design doc comment), SAT-477, SAT-478.
39
-
40
- ## Local trust boundary
41
-
42
- HWM-file integrity (the `ledger.hwm.json` high-water-mark guard introduced in SAT-443) is part of KnoSky's local trust boundary: an attacker who can delete or modify that file — or any file under KnoSky's data directory — already has local write access to the machine, which is equivalent to controlling KnoSky's own code. This is the same boundary D-164 draws for the trust root generally; no fully-local, no-egress tool can defend against an attacker with local filesystem write access without a remote or hardware anchor, which would violate KnoSky's core no-egress design principle.
43
-
44
- ## Scope
45
-
46
- In scope: injection in generated artifacts, secret leakage through projections, the local MCP server, and the indexer's privacy defaults. Out of scope: issues that require an attacker to already control your machine or your repository's contents with your knowledge.
1
+ # Security Policy
2
+
3
+ KnoSky is a **local-first** tool: it runs on your machine, indexes your own folder, and writes a self-contained city file. It does not upload your source and has no hosted backend. The security surface that matters is the **generated artifact** (the city HTML / `city-data.json`) and the local indexer/MCP.
4
+
5
+ ## Supported versions
6
+
7
+ The latest release on the `main` branch is supported. KnoSky is pre-1.0; please run the newest version before reporting an issue.
8
+
9
+ ## Reporting a vulnerability
10
+
11
+ Please report security issues **privately** — do not open a public issue for anything exploitable.
12
+
13
+ - Preferred: GitHub **"Report a vulnerability"** (Security → Advisories) on this repository, which opens a private channel.
14
+ - We aim to acknowledge reports within **5 business days** and to publish a fix or mitigation for confirmed issues as quickly as is practical.
15
+
16
+ When reporting, please include: the version/commit, steps to reproduce, and the impact you observed. A minimal proof-of-concept (e.g., a tiny repo or `city-data.json` that triggers the issue) helps a lot.
17
+
18
+ ## Safe testing guidelines
19
+
20
+ - Test only against repositories and folders **you own or control**.
21
+ - Do not include real secrets or third-party personal data in proof-of-concept artifacts.
22
+ - Do not perform testing that degrades the service for others (there is no shared service; this is a local tool).
23
+
24
+ ## Handling generated artifacts (for users)
25
+
26
+ - A generated city embeds **file names, paths, headings, and short excerpts** — not full file bodies. Treat a generated file as you would a directory listing with snippets.
27
+ - Build with `--share-safe` before sharing: it strips the absolute root path (basename only), runs a fail-closed secret scan, and prints a safety report. Builds **fail closed** if a secret-like value is detected (override only with `--allow-leaks`).
28
+ - Open city files you did not generate the same way you would any untrusted HTML.
29
+
30
+ ## Trust model
31
+
32
+ KnoSky's local trust model applies the core security principles of TUF — role separation, threshold signing, survivable key compromise, and freshness-guaranteed revocation — adapted from TUF's server-oriented update distribution to a fully local, no-egress agentic environment, with attestation formats based on in-toto/DSSE.
33
+
34
+ ## Known boundary conditions
35
+
36
+ **N=2 key-store quorum (D-163):** when a key store holds exactly 2 non-revoked keys, the revocation quorum formula (`Math.floor(peers/2)+1`) reduces to 1, meaning the sole peer key alone can revoke the other. This is mathematically unavoidable at N=2, is an accepted limitation (not a defect), and is explicitly exercised by red-team scenario RT-KS-001 (`test/key-store-quorum-redteam.mjs`). Deployments that require real threshold protection must maintain N≥3 non-revoked keys at all times.
37
+
38
+ **M=0 sole-key self-revocation (D-167):** when a key store has been reduced to exactly 1 non-revoked key (via prior legitimate revocations), `revokeKey()` allows that key to be revoked with zero approvals (`required = 0`). This is an intentional design choice — a *self-wipe*, not a third-party bypass. Requiring peer approval when no peers exist would make a compromised last key permanently irrevocable, which is a worse outcome. D-167's quorum protection is meaningful only at N≥3; this is the N=1 edge of the same quorum curve. **Caller-identity note:** `revokeKey()` does not currently authenticate the caller as the holder of the key being revoked — it authorizes the *state* (M=0), not the *caller*. `revokeKey` has no production call sites today (enforced by an automated whole-repo scan, `test/key-store-quorum-redteam.mjs`), so this is not a live exploitable path. **This is not a simple fix:** each key's raw material (`KeyEntry.raw`) lives inside the same `ks` object `revokeKey` receives, so any caller with access to `ks` already has direct read access to every key's raw material — a self-signature check bolted onto `revokeKey` today would be trivially satisfiable by any caller (they can just read `raw` and compute the expected signature themselves), providing no real security. A genuine fix requires an architectural change — key material stored somewhere the `ks`-holding caller cannot directly read (e.g. an OS keychain or a separate process boundary) — not a signature parameter added to the existing function. Tracked and scoped as such: SAT-478. Explicitly exercised by red-team scenario RT-KS-002 (`test/key-store-quorum-redteam.mjs`). Cross-reference: SAT-472 (quorum design doc comment), SAT-477, SAT-478.
39
+
40
+ ## Local trust boundary
41
+
42
+ HWM-file integrity (the `ledger.hwm.json` high-water-mark guard introduced in SAT-443) is part of KnoSky's local trust boundary: an attacker who can delete or modify that file — or any file under KnoSky's data directory — already has local write access to the machine, which is equivalent to controlling KnoSky's own code. This is the same boundary D-164 draws for the trust root generally; no fully-local, no-egress tool can defend against an attacker with local filesystem write access without a remote or hardware anchor, which would violate KnoSky's core no-egress design principle.
43
+
44
+ ## Opt-in org export (SAT-546)
45
+
46
+ KnoSky's core tool -- everything an `npm install knosky` / `npx knosky` user
47
+ runs (the indexer, the local MCP server, `bin/knosky.mjs`) -- is no-egress by
48
+ default and always, with zero exceptions inside this repository.
49
+
50
+ An organization that wants to also mirror its local append-only checkpoint
51
+ (`core/append-only-checkpoint.mjs`) to storage it owns can do so via a
52
+ **completely separate package and repository**:
53
+ [**knosky-export-daemon**](https://github.com/SathiaAI/knosky-export-daemon).
54
+
55
+ - **Separate package, separate repo, separate install.** This repository does
56
+ not import, require, `dependencies`-reference, or ship any part of
57
+ `knosky-export-daemon` -- enforced by an automated whole-repo test
58
+ (`test/append-only-checkpoint.test.mjs`, F02-020/F02-021: no file or
59
+ directory named "daemon" or matching "export-daemon" exists anywhere in
60
+ this repo, and `package.json` declares no dependency on it). Installing
61
+ `knosky` never installs it; an organization must run a second, deliberate
62
+ `npm install knosky-export-daemon`.
63
+ - **Off by default, explicit config required.** The daemon does nothing
64
+ without a config file the org must create themselves, naming an HTTPS
65
+ destination **the org itself owns** -- never a Sathia/KnoSky-operated
66
+ endpoint. `parseExportConfig` rejects a missing, disabled, or non-HTTPS
67
+ config.
68
+ - **Loud at runtime.** Starting the daemon prints an explicit warning that it
69
+ makes real network calls and is not part of KnoSky's no-egress core tool,
70
+ before doing anything else.
71
+ - **Purpose:** the append-only local file is the guarantee; the export daemon
72
+ in the other repo is an optional convenience layered on top of it for an
73
+ org that wants an off-machine copy -- never a replacement for, or a
74
+ dependency of, the local checkpoint itself.
75
+
76
+ ## Scope
77
+
78
+ In scope: injection in generated artifacts, secret leakage through projections, the local MCP server, and the indexer's privacy defaults. Out of scope: issues that require an attacker to already control your machine or your repository's contents with your knowledge.
@@ -1,89 +1,94 @@
1
- #!/usr/bin/env node
2
- // KnoSky CI3 — GitHub Action glue (KSV2-CI3). Runs knoskyCi() to produce the
3
- // route/safety artifacts, renders the claims-disciplined comment body via
4
- // renderPrComment(), then upserts (creates or updates) ONE PR comment via the
5
- // GitHub REST API using the caller-supplied token.
6
- //
7
- // HARD RULE (matches ci.mjs / D-162 claims discipline): this script must NEVER
8
- // fail the consumer's build on an internal error. The only way this process
9
- // exits non-zero is `fail-on-secret: true` AND knoskyCi() actually finding a
10
- // secret-like pattern in the emitted artifacts — everything else is caught and
11
- // logged, never thrown past main().
12
- import path from 'node:path';
13
- import { fileURLToPath } from 'node:url';
14
-
15
- const HERE = path.dirname(fileURLToPath(import.meta.url));
16
- const CORE = path.resolve(HERE, '..', 'core');
17
- const MARKER = '<!-- knosky-pr-gps -->';
18
-
19
- async function ghFetch(token, url, opts = {}) {
20
- const res = await fetch(url, {
21
- ...opts,
22
- headers: {
23
- Authorization: `Bearer ${token}`,
24
- Accept: 'application/vnd.github+json',
25
- 'X-GitHub-Api-Version': '2022-11-28',
26
- ...(opts.headers || {}),
27
- },
28
- });
29
- if (!res.ok) {
30
- const text = await res.text().catch(() => '');
31
- throw new Error(`GitHub API ${opts.method || 'GET'} ${url} -> ${res.status}: ${text.slice(0, 300)}`);
32
- }
33
- return res.status === 204 ? null : res.json();
34
- }
35
-
36
- async function upsertComment({ token, repo, prNumber, body }) {
37
- const base = `https://api.github.com/repos/${repo}/issues/${prNumber}/comments`;
38
- const existing = await ghFetch(token, `${base}?per_page=100`);
39
- const mine = Array.isArray(existing)
40
- ? existing.find((c) => typeof c.body === 'string' && c.body.includes(MARKER))
41
- : null;
42
- if (mine) {
43
- await ghFetch(token, `https://api.github.com/repos/${repo}/issues/comments/${mine.id}`, {
44
- method: 'PATCH',
45
- body: JSON.stringify({ body }),
46
- });
47
- console.log('KnoSky action: updated the existing PR-GPS comment.');
48
- } else {
49
- await ghFetch(token, base, { method: 'POST', body: JSON.stringify({ body }) });
50
- console.log('KnoSky action: posted a new PR-GPS comment.');
51
- }
52
- }
53
-
54
- async function main() {
55
- const env = process.env;
56
- const token = env.GITHUB_TOKEN;
57
- const repo = env.KC_REPO || '';
58
- const prNumber = env.KC_PR_NUMBER || '';
59
-
60
- const { knoskyCi } = await import(path.join(CORE, 'ci.mjs'));
61
- const { renderPrComment } = await import(path.join(CORE, 'pr-comment.mjs'));
62
-
63
- const { exitCode, routeJson, safetyJson } = await knoskyCi({
64
- root: env.KC_ROOT || '.',
65
- base: env.KC_BASE,
66
- head: env.KC_HEAD,
67
- cityPath: env.KC_CITY,
68
- failOnSecret: env.KC_FAIL_ON_SECRET === 'true',
69
- });
70
-
71
- if (!token || !repo || !prNumber) {
72
- // Not a pull_request context (or the caller didn't wire the inputs) —
73
- // nothing to comment on. Advisory tool, nothing to do, exit clean.
74
- console.log('KnoSky action: no PR context (repo/PR number/token missing) — skipping the comment.');
75
- return exitCode;
76
- }
77
-
78
- const body = MARKER + '\n' + renderPrComment({ routeJson, safetyJson });
79
- await upsertComment({ token, repo, prNumber, body });
80
-
81
- return exitCode;
82
- }
83
-
84
- main()
85
- .then((code) => { process.exitCode = code || 0; })
86
- .catch((err) => {
87
- console.error('KnoSky action: internal error (advisory-only — not failing the build):', err && err.message);
88
- process.exitCode = 0;
89
- });
1
+ #!/usr/bin/env node
2
+ // KnoSky CI3 — GitHub Action glue (KSV2-CI3). Runs knoskyCi() to produce the
3
+ // route/safety artifacts, renders the claims-disciplined comment body via
4
+ // renderPrComment(), then upserts (creates or updates) ONE PR comment via the
5
+ // GitHub REST API using the caller-supplied token.
6
+ //
7
+ // HARD RULE (matches ci.mjs / D-162 claims discipline): this script must NEVER
8
+ // fail the consumer's build on an internal error. The only way this process
9
+ // exits non-zero is `fail-on-secret: true` AND knoskyCi() actually finding a
10
+ // secret-like pattern in the emitted artifacts — everything else is caught and
11
+ // logged, never thrown past main().
12
+ import path from 'node:path';
13
+ import { fileURLToPath, pathToFileURL } from 'node:url';
14
+
15
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
16
+ const CORE = path.resolve(HERE, '..', 'core');
17
+ const MARKER = '<!-- knosky-pr-gps -->';
18
+
19
+ /** Dynamic import that works with Windows absolute paths (file:// required). */
20
+ function importCore(name) {
21
+ return import(pathToFileURL(path.join(CORE, name)).href);
22
+ }
23
+
24
+ async function ghFetch(token, url, opts = {}) {
25
+ const res = await fetch(url, {
26
+ ...opts,
27
+ headers: {
28
+ Authorization: `Bearer ${token}`,
29
+ Accept: 'application/vnd.github+json',
30
+ 'X-GitHub-Api-Version': '2022-11-28',
31
+ ...(opts.headers || {}),
32
+ },
33
+ });
34
+ if (!res.ok) {
35
+ const text = await res.text().catch(() => '');
36
+ throw new Error(`GitHub API ${opts.method || 'GET'} ${url} -> ${res.status}: ${text.slice(0, 300)}`);
37
+ }
38
+ return res.status === 204 ? null : res.json();
39
+ }
40
+
41
+ async function upsertComment({ token, repo, prNumber, body }) {
42
+ const base = `https://api.github.com/repos/${repo}/issues/${prNumber}/comments`;
43
+ const existing = await ghFetch(token, `${base}?per_page=100`);
44
+ const mine = Array.isArray(existing)
45
+ ? existing.find((c) => typeof c.body === 'string' && c.body.includes(MARKER))
46
+ : null;
47
+ if (mine) {
48
+ await ghFetch(token, `https://api.github.com/repos/${repo}/issues/comments/${mine.id}`, {
49
+ method: 'PATCH',
50
+ body: JSON.stringify({ body }),
51
+ });
52
+ console.log('KnoSky action: updated the existing PR-GPS comment.');
53
+ } else {
54
+ await ghFetch(token, base, { method: 'POST', body: JSON.stringify({ body }) });
55
+ console.log('KnoSky action: posted a new PR-GPS comment.');
56
+ }
57
+ }
58
+
59
+ async function main() {
60
+ const env = process.env;
61
+ const token = env.GITHUB_TOKEN;
62
+ const repo = env.KC_REPO || '';
63
+ const prNumber = env.KC_PR_NUMBER || '';
64
+
65
+ const { knoskyCi } = await importCore('ci.mjs');
66
+ const { renderPrComment } = await importCore('pr-comment.mjs');
67
+
68
+ const { exitCode, routeJson, safetyJson } = await knoskyCi({
69
+ root: env.KC_ROOT || '.',
70
+ base: env.KC_BASE,
71
+ head: env.KC_HEAD,
72
+ cityPath: env.KC_CITY,
73
+ failOnSecret: env.KC_FAIL_ON_SECRET === 'true',
74
+ });
75
+
76
+ if (!token || !repo || !prNumber) {
77
+ // Not a pull_request context (or the caller didn't wire the inputs) —
78
+ // nothing to comment on. Advisory tool, nothing to do, exit clean.
79
+ console.log('KnoSky action: no PR context (repo/PR number/token missing) skipping the comment.');
80
+ return exitCode;
81
+ }
82
+
83
+ const body = MARKER + '\n' + renderPrComment({ routeJson, safetyJson });
84
+ await upsertComment({ token, repo, prNumber, body });
85
+
86
+ return exitCode;
87
+ }
88
+
89
+ main()
90
+ .then((code) => { process.exitCode = code || 0; })
91
+ .catch((err) => {
92
+ console.error('KnoSky action: internal error (advisory-only — not failing the build):', err && err.message);
93
+ process.exitCode = 0;
94
+ });