artifacty 0.11.1 → 0.12.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.
package/CLAUDE.md CHANGED
@@ -2,10 +2,12 @@
2
2
 
3
3
  This file provides guidance to Claude Code when working with this repository.
4
4
 
5
- Artifacty is a local, agent-to-agent artifact exchange for LLM workflows. One agent publishes an artifact once; others list, read, import, update, and continue from it without copying content through chat. It exposes one shared store over HTTP/browser, CLI, and MCP stdio surfaces.
5
+ Artifacty is a local, agent-to-agent artifact exchange for LLM workflows. One agent publishes an artifact. Other agents can list, read, import, update, and continue from it without copying content through chat. HTTP, the browser, the CLI, and MCP stdio use the same store.
6
6
 
7
7
  See `AGENTS.md` for repository structure, coding-style conventions, and commit/PR guidelines.
8
8
 
9
+ For English and Korean documentation, follow the [writing style guide](docs/writing-style.md). Use clear sentences and consistent terms. Keep technical vocabulary precise. Write Korean in natural, fluent Korean rather than translating English sentence patterns.
10
+
9
11
  ## Commands
10
12
 
11
13
  - `npm test` — run all tests with `node --test`.
@@ -39,7 +41,7 @@ The store lives at `ARTIFACTY_HOME` or `~/.artifacty` by default.
39
41
  - `server.json` records the currently running browser server URL so CLI and MCP responses keep working when the default port falls back.
40
42
  - Legacy `index.json` stores migrate automatically on first access.
41
43
 
42
- Versions are append-only. Do not mutate prior content files, except through the audited admin repair (`replaceArtifactVersion`) and delete (`deleteArtifactVersion`) paths, which exist specifically to correct bad historical versions and record every change in the audit log.
44
+ Versions are append-only. To correct a historical version, use the admin repair (`replaceArtifactVersion`) or delete (`deleteArtifactVersion`) path. These paths record every change in the audit log. Do not modify prior content files through other paths.
43
45
 
44
46
  ### Security Model
45
47
 
@@ -51,7 +53,7 @@ Versions are append-only. Do not mutate prior content files, except through the
51
53
 
52
54
  ### Browser Editor
53
55
 
54
- The create/import/edit screens use CodeMirror 6 for Markdown, HTML, JSON, and text editing. CodeMirror assets are served locally through allowlisted `/vendor/npm/*` routes, not from a CDN. UI text defaults to English and supports Korean with `?lang=ko`; documentation stays English-only.
56
+ The create/import/edit screens use CodeMirror 6 for Markdown, HTML, JSON, and text editing. CodeMirror assets are served locally through allowlisted `/vendor/npm/*` routes, not from a CDN. UI text defaults to English and supports Korean with `?lang=ko`. Keep each document in its existing language unless a translation is requested.
55
57
 
56
58
  ### MCP Server
57
59
 
package/README.md CHANGED
@@ -5,18 +5,21 @@
5
5
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
6
  [![Node.js >=22.5](https://img.shields.io/badge/node-%3E%3D22.5-339933.svg)](package.json)
7
7
 
8
- Artifacty is a local, agent-to-agent artifact exchange for LLM workflows. Claude, Codex, Gemini, GitHub Copilot, Cursor, and other MCP-capable tools can publish an artifact once, then other agents can list, read, update, and continue from it without copying content through chat.
8
+ Artifacty gives AI-generated artifacts a local home that agents can share. Claude, Codex, Gemini, GitHub Copilot, Cursor, and other MCP tools can publish artifacts to the same store. Other agents can find, read, and update them without copying content through chat.
9
9
 
10
10
  ![Artifacty overview showing multiple AI agents sharing artifacts through a local exchange](docs/assets/artifacty.png)
11
11
 
12
12
  ## Why MCP
13
13
 
14
- Claude Code artifacts are useful because they turn session output into shareable, versioned pages. Artifacty keeps that local and cross-agent: the browser server renders artifacts for people, while the MCP stdio server gives agents a common tool interface.
14
+ Artifacts turn session output into shareable, versioned pages. Artifacty keeps those pages local and makes them available across agents. The browser server renders artifacts for people. The MCP stdio server gives agents a common tool interface.
15
15
 
16
16
  ## Installation
17
17
 
18
18
  Artifacty requires Node.js 22.5 or newer.
19
19
 
20
+ The optional React renderer uses Babel 8. Its supported Node.js versions are
21
+ `^22.18.0 || >=24.11.0`. Use a compatible version if you enable that renderer.
22
+
20
23
  Install the CLI globally from npm:
21
24
 
22
25
  ```bash
@@ -36,7 +39,7 @@ Start the local dashboard in the background:
36
39
  artifacty serve
37
40
  ```
38
41
 
39
- Open the `url` printed in the JSON response. Artifacty prefers `http://127.0.0.1:8787`; if that default port is busy and no explicit port was configured, it starts on the next available local port and records the actual URL for CLI and MCP responses.
42
+ Open the `url` in the JSON response. Artifacty prefers `http://127.0.0.1:8787`. If that port is busy and you did not configure a port, Artifacty uses the next available local port. It records the actual URL for CLI and MCP responses.
40
43
 
41
44
  Manage the background server:
42
45
 
@@ -45,8 +48,8 @@ artifacty status
45
48
  artifacty stop
46
49
  ```
47
50
 
48
- `artifacty start` and `artifacty serve --detach` use the same lifecycle path as `artifacty serve`. Logs are written under `~/.artifacty/logs/`.
49
- These lifecycle commands use Node's detached process support and work on macOS, Linux, and Windows. `artifacty stop` uses process-group signals on macOS/Linux and Windows `taskkill`, falling back to `/F` when Windows requires forceful termination.
51
+ `artifacty start` and `artifacty serve --detach` use the same lifecycle path as `artifacty serve`. Artifacty writes logs under `~/.artifacty/logs/`.
52
+ These commands use Node's detached process support on macOS, Linux, and Windows. `artifacty stop` uses process-group signals on macOS and Linux. On Windows, it uses `taskkill`. If Windows requires forceful termination, it retries with `/F`.
50
53
 
51
54
  For foreground debugging, keep the process attached:
52
55
 
@@ -174,7 +177,7 @@ them as HTML instead of plain text.
174
177
 
175
178
  ## Agent Handoff Example
176
179
 
177
- One agent can publish a continuation artifact, then another agent can discover it, read the context, and append the next version.
180
+ One agent publishes a handoff artifact. Another agent finds it, reads the context, and appends the next version.
178
181
 
179
182
  Codex publishes the handoff:
180
183
 
@@ -259,7 +262,9 @@ By default Artifacty stores files under `~/.artifacty`.
259
262
  ARTIFACTY_HOME=/path/to/shared/store artifacty serve
260
263
  ```
261
264
 
262
- Artifact metadata is stored in `artifacty.sqlite`; artifact content is stored as append-only version files under `artifacts/` for normal create and update flows. Administrators can repair or delete individual bad versions from the browser, and those exceptional actions are recorded in the audit log. The current browser server URL is written to `server.json` so MCP tools can return the correct links when the default port falls back. Existing `index.json` stores are migrated automatically on first access.
265
+ Artifacty stores metadata in `artifacty.sqlite`. Normal create and update operations append version files under `artifacts/`. Administrators can repair or delete individual versions from the browser. The audit log records these exceptions.
266
+
267
+ Artifacty writes the current browser server URL to `server.json`. MCP tools use this URL to return correct links when the server selects another port. On first access, Artifacty automatically migrates existing `index.json` stores.
263
268
 
264
269
  Every artifact row carries a `visibility` (`private`/`team`, default `team`) and an `ownerUserId`, backfilled from the artifact's `publisherUserId` on migration. See the Security Model section below for the access rules these enforce.
265
270
 
@@ -286,7 +291,7 @@ as artifacts-only. For large migrations or scripted server moves, use
286
291
  `artifacty backup --full` and
287
292
  `artifacty import-store --file ./artifacty-backup.json --confirm replace-all`.
288
293
 
289
- Search uses a SQLite FTS5 index when the local Node SQLite build supports it. The index covers the latest version body plus title, tags, source agent, artifact type, format, and metadata summary. If FTS5 is unavailable, Artifacty keeps working with metadata search. Rebuild or check the store when needed:
294
+ If the local Node SQLite build supports FTS5, search uses a SQLite FTS5 index. It covers the latest version body, title, tags, source agent, artifact type, format, and metadata summary. If FTS5 is unavailable, Artifacty uses metadata search. To rebuild or check the store, run:
290
295
 
291
296
  ```bash
292
297
  artifacty index rebuild
@@ -314,7 +319,7 @@ export ARTIFACTY_EMBEDDINGS_API_KEY=sk-...
314
319
  export ARTIFACTY_EMBEDDINGS_COMMAND="python3 embed.py"
315
320
  ```
316
321
 
317
- The API key is read from the environment only; it is never logged, returned in an error, or persisted to the store (`artifacty doctor` reports the provider and model with the key redacted). New artifacts are embedded automatically in the background after create/update, and existing ones can be (re-)embedded in batches of 16 with:
322
+ Artifacty reads the API key only from the environment. It does not log the key, return it in errors, or save it to the store. `artifacty doctor` reports the provider and model with the key redacted. After a create or update, Artifacty generates embeddings in the background. To generate or refresh embeddings for existing artifacts in batches of 16, run:
318
323
 
319
324
  ```bash
320
325
  artifacty index rebuild --embeddings
@@ -322,7 +327,7 @@ artifacty index rebuild --embeddings
322
327
 
323
328
  ### Relations
324
329
 
325
- Artifacts can be linked with typed, directional relations so a handoff, its review, and follow-up work stay discoverable from one another:
330
+ Use typed, directional relations to connect a handoff, its review, and follow-up work:
326
331
 
327
332
  - Relation names are a closed set for v1: `derived-from`, `supersedes`, `reviews`, `references`, `part-of`.
328
333
  - Every relation has a computed inverse (`derived-from` ↔ `derives`, `supersedes` ↔ `superseded-by`, `reviews` ↔ `reviewed-by`, `references` ↔ `referenced-by`, `part-of` ↔ `contains`) shown automatically on the other side.
@@ -339,12 +344,12 @@ artifacty unlink <from-id> derived-from <to-id>
339
344
 
340
345
  ### Comments and review status
341
346
 
342
- Lightweight, version-anchored feedback without publishing a whole new version or a separate review artifact:
347
+ Add feedback to a specific version without publishing a new version or a separate review artifact:
343
348
 
344
- - Comments are Markdown, rendered through the same sanitized Markdown pipeline as artifact content, and capped at 16 KB. `anchor` is an optional, format-specific rendering hint — `{ line: 42 }` for text formats, `{ path: "$.runs[0].results[3]" }` for JSON/SARIF, `{ row: 7 }` for CSV — and is not validated against the artifact's actual content.
349
+ - Comments use the same sanitized Markdown renderer as artifact content. Each comment has a 16 KB limit. The optional `anchor` field provides a display hint: `{ line: 42 }` for text, `{ path: "$.runs[0].results[3]" }` for JSON/SARIF, or `{ row: 7 }` for CSV. Artifacty does not validate the hint against the content.
345
350
  - Threads are one level deep: `parentId` on a new comment must point to a root comment (one with no `parentId` of its own).
346
351
  - Deleting a comment is a soft delete: it disappears from `artifacty comments`/`GET .../comments` but its audit-log entry is kept. Pass `--include-deleted`/`includeDeleted=true` to see it anyway.
347
- - Every artifact carries a `reviewStatus`: `none`, `pending`, `changes-requested`, or `approved`, set explicitly and reset to `pending` automatically (noted in that update's audit metadata) whenever a new version is appended after an `approved` status.
352
+ - Each artifact has a `reviewStatus`: `none`, `pending`, `changes-requested`, or `approved`. Set the status explicitly. If you append a version to an approved artifact, Artifacty resets the status to `pending`. The update's audit metadata records this change.
348
353
  - `GET`/`POST /api/artifacts/:id/comments`, `POST /api/artifacts/:id/comments/:commentId/resolve`, `DELETE /api/artifacts/:id/comments/:commentId`, and `POST /api/artifacts/:id/review-status` manage comments and review status over HTTP.
349
354
  - `artifacty_comment`, `artifacty_resolve_comment`, and `artifacty_set_review_status` are the MCP equivalents; `artifacty_get` accepts `includeComments: true` to return the requested version's open comments alongside the artifact.
350
355
  - The browser artifact page shows a comments panel grouped by version, with a reply form, a resolve button, and (for the artifact's owner or an admin) a review-status selector. Line-anchored comments are shown as plain text (e.g. "line 42") rather than a CodeMirror gutter marker.
@@ -358,7 +363,9 @@ artifacty review-status release-handoff-abc12345 approved
358
363
 
359
364
  ### Diff
360
365
 
361
- `artifacty diff <id> [--from N] [--to M] [--structured] [--json]` compares two versions of an artifact, defaulting `--to` to the latest version and `--from` to `--to - 1`. JSON, SARIF, CSV, notebook, and bundle artifacts get a structured diff (JSON path entries, CSV row/cell entries, or per-file bundle entries) by default; other formats print a line diff. `--structured` forces the structured (word-highlighted line) diff for any format; `--json` prints the machine-readable form instead of the human-readable unified text.
366
+ `artifacty diff <id> [--from N] [--to M] [--structured] [--json]` compares two artifact versions. By default, `--to` selects the latest version and `--from` selects `--to - 1`.
367
+
368
+ JSON, SARIF, CSV, notebook, and bundle artifacts use a structured diff by default. The output contains JSON paths, CSV rows and cells, or bundle files. Other formats use a line diff. To request a structured diff for any format, use `--structured`; text lines include word highlights. To print machine-readable output instead of unified text, use `--json`.
362
369
 
363
370
  ```bash
364
371
  artifacty diff release-handoff-abc12345
@@ -367,14 +374,16 @@ artifacty diff release-handoff-abc12345 --from 1 --to 3 --json
367
374
 
368
375
  ### Retention
369
376
 
370
- Artifacts, audit rows, and events grow without bound by default. A declarative retention policy, stored in the `meta` table and edited through `/admin/retention` or `artifacty retention set`, lets an admin bound that growth:
377
+ By default, Artifacty does not limit retained artifacts, audit rows, or events. Administrators can set a retention policy at `/admin/retention` or with `artifacty retention set`. Artifacty stores the policy in the `meta` table:
371
378
 
372
379
  - `archiveAfterDays` (a default plus optional per-artifact-type overrides) auto-archives artifacts that have not been updated within the window, unless the artifact carries a tag in `keepTags` or has `reviewStatus: "approved"`.
373
380
  - `purgeArchivedAfterDays` hard-deletes artifacts that have been archived longer than the window, including their version files, dependent rows that cascade via foreign keys (comments, relations, embeddings), and their `events`/search-index rows. Audit log rows for the artifact are deliberately kept (see `auditRetentionDays` below) as the historical record that the purge happened, even though the artifact itself is gone. This is the only retention action that deletes data outright, so it is refused unless the operator has set `ARTIFACTY_RETENTION_ALLOW_PURGE=true` in the server environment; `--allow-purge` (or the `allowPurge` request field) only opts a single run in and never sets that environment variable itself. Always dry-run first (`artifacty retention run --dry-run`, or the report on `/admin/retention`) to review what would be purged.
374
381
  - `auditRetentionDays` prunes old audit log rows, except `version-repair`, `version-delete`, `retention-purge`, `retention-policy-update`, and `owner-change`, which are kept indefinitely.
375
382
  - `eventRetentionRows` caps how many rows the `events` change-notification table keeps.
376
383
 
377
- A background sweep runs every `ARTIFACTY_RETENTION_INTERVAL_MS` (default one hour) alongside the HTTP server, checking the current policy on every tick — it starts even when the policy is all-unset, so setting a policy later takes effect on the next tick without a restart. `artifacty integrity`/`checkStoreIntegrity` flags any files left behind if a purge is ever interrupted, and `artifacty doctor` reports the current retention policy summary.
384
+ The HTTP server runs a background sweep every `ARTIFACTY_RETENTION_INTERVAL_MS` (default one hour). Each sweep checks the current policy. The timer starts even if no policy is set. A later policy change takes effect on the next sweep without a restart.
385
+
386
+ If a purge is interrupted, `artifacty integrity`/`checkStoreIntegrity` reports files left behind. `artifacty doctor` reports the current retention policy summary.
378
387
 
379
388
  ```bash
380
389
  artifacty retention show
@@ -439,7 +448,9 @@ curl -s -X POST http://127.0.0.1:8787/api/webhooks \
439
448
  -d '{ "url": "https://example.com/hooks/artifacty", "eventTypes": ["artifact.updated"] }'
440
449
  ```
441
450
 
442
- The response includes `secret` once — store it; it is never returned again. Deliveries are `POST` JSON with headers `X-Artifacty-Event`, `X-Artifacty-Delivery`, and `X-Artifacty-Signature: sha256=<hmac>`, retried up to 3 times (2s/10s/60s backoff) and disabled after 20 consecutive failures. Manage webhooks at `/admin/webhooks` in the browser, or `GET/POST /api/webhooks`, `DELETE /api/webhooks/:id`, `POST /api/webhooks/:id/test`. See [docs/threat-model.md](docs/threat-model.md) for the signature scheme and SSRF guard.
451
+ Save `secret` from the response. Artifacty returns it only once. Each delivery sends JSON with `POST` and these headers: `X-Artifacty-Event`, `X-Artifacty-Delivery`, and `X-Artifacty-Signature: sha256=<hmac>`. Failed deliveries retry up to three times, after 2, 10, and 60 seconds. After 20 consecutive failures, Artifacty disables the webhook.
452
+
453
+ Manage webhooks at `/admin/webhooks` in the browser. The API provides `GET/POST /api/webhooks`, `DELETE /api/webhooks/:id`, and `POST /api/webhooks/:id/test`. See [docs/threat-model.md](docs/threat-model.md) for the signature scheme and SSRF guard.
443
454
 
444
455
  **MCP:** `resources/subscribe` on `artifacty://artifacts/{id}` or `artifacty://recent` delivers `notifications/resources/updated` over the stdio transport. The `artifacty_wait` tool (`{ artifactId?, tag?, type?, timeoutMs }`, max 120000ms) is a long-poll alternative that works over any transport, including the stateless `/mcp` HTTP transport where `resources/subscribe` cannot push notifications. See [docs/mcp-public-api.md](docs/mcp-public-api.md).
445
456
 
@@ -519,11 +530,13 @@ If the artifact's `latestVersion` no longer matches, the update is rejected with
519
530
  { "error": "Version conflict", "code": "version_conflict", "details": { "latestVersion": 4 } }
520
531
  ```
521
532
 
522
- `GET /api/artifacts/:id` sets an `ETag` header and returns `304 Not Modified` when the request's `If-None-Match` matches the current version. `expectedVersion` is optional, so clients that don't send it keep working exactly as before. The browser editor sends its own hidden `expectedVersion` field and shows a banner with a link to the diff view if another agent updated the artifact first.
533
+ `GET /api/artifacts/:id` sets an `ETag` header. If the request's `If-None-Match` matches the current version, the server returns `304 Not Modified`. The optional `expectedVersion` field preserves compatibility with clients that omit it.
534
+
535
+ The browser editor sends a hidden `expectedVersion` field. If another agent updated the artifact first, the editor shows a conflict banner with a link to the diff view.
523
536
 
524
537
  ## Interface Language
525
538
 
526
- The browser UI defaults to English. Add `?lang=ko` to any browser route to use Korean, for example `http://127.0.0.1:8787/new?lang=ko`. Forms and in-app links preserve the selected language. Documentation is maintained in English only.
539
+ The browser UI defaults to English. Add `?lang=ko` to any browser route to use Korean, for example `http://127.0.0.1:8787/new?lang=ko`. Forms and in-app links preserve the selected language. The current product guides are in English. English and Korean documentation follow the [documentation writing guide](docs/writing-style.md). Korean prose should use natural Korean phrasing.
527
540
 
528
541
  Schema and storage:
529
542
 
@@ -541,14 +554,127 @@ Schema and storage:
541
554
  - See [docs/sarif-csv-artifact-plan.md](docs/sarif-csv-artifact-plan.md) for the SARIF/CSV output artifact roadmap.
542
555
  - See [docs/roadmap-design.md](docs/roadmap-design.md) for the design of planned features such as relations, change notifications, optimistic concurrency, comments, semantic search, visibility, token scopes, and retention.
543
556
 
557
+ ## Public and Private Access Modes
558
+
559
+ This feature upgrades the SQLite store to version 10. Older builds reject the
560
+ upgraded store rather than ignoring its access policy.
561
+
562
+ Administrators can open **Account → Access** (`/admin/access`) to change the
563
+ server's access mode without restarting it. Existing installations default to
564
+ **public** mode, preserving anonymous dashboard access to team-visible artifacts;
565
+ artifacts marked private remain restricted to their owner and administrators.
566
+ API authentication and write protection still apply in public mode.
567
+
568
+ In **private** mode, browser visitors must sign in. An artifact is readable only
569
+ by its owner, an administrator, or users explicitly granted access, directly or
570
+ through a team. The existing `team` visibility value does not grant all users
571
+ access in this mode. Sharing grants support **read**, **comment**, and **edit**.
572
+ Commenters can add feedback and resolve their own comments; editors can also
573
+ append versions. The strongest direct or team grant applies. Ownership, sharing,
574
+ archiving, and review-status control remain with owners and administrators.
575
+ Administrators can access existing unowned artifacts. They can assign ownership
576
+ or share those artifacts.
577
+
578
+ Create teams on `/admin/access`. Select the members for each team.
579
+
580
+ To grant artifact access:
581
+
582
+ 1. Open the artifact as its owner or an administrator.
583
+ 2. Open **Sharing**.
584
+ 3. Select the allowed users and teams.
585
+ 4. Select their permission levels.
586
+ 5. Save the sharing settings.
587
+
588
+ The **Effective access** report shows each
589
+ user's actual permission and why it applies, anonymous access, and the global IP
590
+ condition. Removing a grant or team membership takes effect on subsequent reads,
591
+ search results, and change notifications. Personal API tokens carry the same
592
+ user permissions through HTTP and MCP. Shared server tokens have no user
593
+ identity and cannot read or create artifacts through ordinary artifact routes in
594
+ private mode. Trusted local CLI/store access remains an administrative
595
+ filesystem capability.
596
+
597
+ The same admin page accepts an optional IP/CIDR allowlist, one address or network
598
+ per line (for example `192.168.10.0/24` or `2001:db8:1234::/48`). If the list is empty,
599
+ all source addresses are allowed. If the list has entries, it restricts **all HTTP routes**
600
+ in both modes. This includes login, raw downloads, API routes, and HTTP MCP.
601
+ IP permission does not replace user or artifact permissions. Saving a list that
602
+ excludes the administrator's current connection address is rejected. Browser
603
+ changes are provisional for **120 seconds**: confirm that access still works to
604
+ keep the change, or roll it back. Unconfirmed changes revert before the next
605
+ policy check after the deadline, even across server restarts. A second change is
606
+ blocked while confirmation is pending. Mode changes offer a preview of how many
607
+ artifacts will become anonymously readable before you apply them.
608
+
609
+ Address checks use the actual TCP peer, including IPv4-mapped IPv6 addresses;
610
+ `X-Forwarded-For` and `Forwarded` are not trusted. Behind a reverse proxy, the
611
+ peer is the proxy: enforce end-user IP restrictions at that proxy. Local stdio
612
+ MCP and CLI operations do not have an HTTP source IP and are not covered by the
613
+ network allowlist.
614
+
615
+ Full backups include modes, network settings, teams, memberships, sharing grants,
616
+ and inbox entries. Pending network confirmation state and share-link tokens are
617
+ not exported. Artifact-only restores do not restore grants. If they replace existing grants,
618
+ they require confirmation. Restoring a full backup into a private server
619
+ keeps it private even if the backup was public, and an existing network policy
620
+ is preserved.
621
+
622
+ If you switch back to public mode, anonymous visitors can read team-visible
623
+ artifacts again. Artifacty retains explicit sharing grants for a later return
624
+ to private mode.
625
+
626
+ ## Share Links and Inbox
627
+
628
+ Owners and administrators can create a read-only link from an artifact's
629
+ **Sharing** page:
630
+
631
+ 1. Choose the latest version or pin a particular version.
632
+ 2. Set an expiration (seven days by default).
633
+ 3. Copy the URL when it is first shown.
634
+
635
+ Only a hash is stored, so the complete URL is not shown again. Links can be
636
+ revoked individually and are permanently revoked on ownership transfer.
637
+ Archived artifacts and deleted versions cannot be read through their links.
638
+
639
+ Links grant access to the selected artifact without an account, so private mode
640
+ requires an administrator to enable **Allow share links in private mode** at
641
+ `/admin/access`. Disabling this policy also disables existing links while the
642
+ server is private. Every link request still obeys the network allowlist. Links
643
+ do not expose the dashboard, history, comments, or editing controls. Public HTML
644
+ previews are static: scripts and external resources are disabled. React,
645
+ Mermaid, SVG and notebook shares show source instead of running active content.
646
+
647
+ The **Inbox** (`/inbox`) lists newly shared artifacts, comments, review requests,
648
+ and new versions for the signed-in user. Events caused by the same user are
649
+ excluded. Read individual notifications or mark all as read. Revoking artifact
650
+ access also hides its notifications and removes them from unread counts.
651
+
652
+ ## Sample Manuals
653
+
654
+ Install four English sample artifacts: **Start here**, **Feature guide**,
655
+ **Architecture**, and **Handoff exercise**. Administrators can use the sample
656
+ controls on `/admin/access`; local CLI users can run:
657
+
658
+ ```bash
659
+ artifacty samples install
660
+ artifacty samples remove
661
+ ```
662
+
663
+ Installation is idempotent within the installing account (or local CLI scope).
664
+ Reinstalling restores archived samples without overwriting edits. Removal
665
+ archives managed samples and preserves their versions; it does not delete other
666
+ artifacts with similar names or tags. In private mode, share the installed
667
+ manuals with the intended users or teams from each artifact's Sharing page.
668
+
544
669
  ## Security Model
545
670
 
546
671
  - The HTTP server binds to `127.0.0.1` by default.
547
672
  - If `ARTIFACTY_API_TOKEN` is set, HTTP API routes require `Authorization: Bearer <token>` or `x-artifacty-token`; scripts should prefer headers over `?token=...` URLs.
548
673
  - When users exist, personal API tokens issued from `/account` also authenticate HTTP API and MCP requests. Created artifacts record the token owner's email as `publisherId`, and audit logs record the same identity as `actor`.
549
674
  - API token checks use timing-safe digest comparison.
550
- - Personal API tokens carry scopes (`read`, `write`, `admin`; default `read`+`write`), settable as checkboxes when creating a token from `/account`. `admin` is only grantable to an admin user's token. Every `/api/*` route requires the matching scope on the authenticating token; the shared `ARTIFACTY_API_TOKEN` and browser sessions always have full scopes. A denial responds `403` with `code: "scope_denied"` and writes a rate-limited `token-scope-denied` audit row. Over MCP, `tools/list` omits mutating tools for a token without `write`, and calling one anyway returns an error result with `structuredContent.code: "scope_denied"`.
551
- - Once any user account exists, every artifact has a `visibility` (`private` or `team`, defaulting to `team`) and an `ownerUserId`. `team` is readable and writable by any authenticated principal (unless `ARTIFACTY_TEAM_WRITE=owner` restricts writes to the owner or an admin). `private` is readable and writable only by its owner or an admin; reads by anyone else 404 instead of 403 so the artifact's existence isn't leaked. Archiving, restoring, and changing visibility or ownership always require the owner or an admin, regardless of visibility. A shared `ARTIFACTY_API_TOKEN` (or any request before the first user account is created) has no personal identity and is treated as an anonymous team principal: it can read and write `team` artifacts but never sees `private` ones. In single-user mode (no user accounts at all) every check is bypassed, matching pre-visibility behavior. Change visibility and ownership with `POST /api/artifacts/:id/visibility` and `POST /api/artifacts/:id/owner`, the `artifacty visibility <id> private|team` CLI command, or the `artifacty_set_visibility` MCP tool.
675
+ - Personal API tokens support `read`, `write`, and `admin` scopes. The default is `read`+`write`. Select scopes when creating a token at `/account`. Only administrator tokens can receive `admin`. Each `/api/*` route requires the matching token scope. The shared `ARTIFACTY_API_TOKEN` and browser sessions have full scopes.
676
+ If a scope check fails, the server returns `403` with `code: "scope_denied"`. It also writes a rate-limited `token-scope-denied` audit row. For MCP tokens without `write`, `tools/list` omits tools that change data. Calls to those tools return `structuredContent.code: "scope_denied"`.
677
+ - Once any user account exists, every artifact has a `visibility` (`private` or `team`, defaulting to `team`) and an `ownerUserId`. In public access mode, `team` is readable and writable by any authenticated principal (unless `ARTIFACTY_TEAM_WRITE=owner` restricts writes to the owner or an admin). `private` is readable and writable only by its owner or an admin; reads by anyone else 404 instead of 403 so the artifact's existence isn't leaked. Archiving, restoring, and changing visibility or ownership always require the owner or an admin, regardless of visibility. A shared `ARTIFACTY_API_TOKEN` (or any request before the first user account is created) has no personal identity and is treated as an anonymous team principal: it can read and write `team` artifacts but never sees `private` ones. In single-user mode (no user accounts at all) every check is bypassed, matching pre-visibility behavior. Change visibility and ownership with `POST /api/artifacts/:id/visibility` and `POST /api/artifacts/:id/owner`, the `artifacty visibility <id> private|team` CLI command, or the `artifacty_set_visibility` MCP tool.
552
678
  - HTTP and MCP-over-HTTP requests are rate limited per `(token or user or remote address, bucket)` in fixed one-minute windows: `write` (mutating routes, default 120/min, `ARTIFACTY_RATE_WRITE_PER_MIN`), `auth` (`/login`, default 10/min per address, `ARTIFACTY_RATE_AUTH_PER_MIN`), and `search` (`GET /api/artifacts?q=`, default 300/min, `ARTIFACTY_RATE_SEARCH_PER_MIN`). Rate limiting is disabled on a loopback bind unless `ARTIFACTY_RATE_LIMIT=always`, and always disabled with `ARTIFACTY_RATE_LIMIT=off`. Exceeding a limit returns `429` with a `Retry-After` header, `code: "rate_limited"`, and a rate-limited `rate-limited` audit row.
553
679
  - Binding outside localhost requires both `ARTIFACTY_SHARE_MODE=lan` or `team` and `ARTIFACTY_API_TOKEN`.
554
680
  - Non-local sharing is intended for trusted LAN or VPN sessions. Prefer a specific interface IP over `0.0.0.0`, keep React rendering disabled, and see [docs/network-sharing.md](docs/network-sharing.md).
@@ -574,7 +700,7 @@ See [SECURITY.md](SECURITY.md), [docs/threat-model.md](docs/threat-model.md), an
574
700
 
575
701
  ## Environment Variables
576
702
 
577
- Every `ARTIFACTY_*` variable Artifacty reads, most already covered in context above. `node src/cli.js help` prints the same list.
703
+ The table lists every `ARTIFACTY_*` variable that Artifacty reads. `node src/cli.js help` prints the same list.
578
704
 
579
705
  | Variable | Purpose | Default |
580
706
  | --- | --- | --- |
package/SECURITY.md CHANGED
@@ -2,18 +2,18 @@
2
2
 
3
3
  ## Supported Versions
4
4
 
5
- Artifacty is pre-1.0. Security fixes are provided for the latest published
6
- minor release. Upgrade to the latest `artifacty` version before reporting a
5
+ Artifacty is pre-1.0. The project provides security fixes for the latest
6
+ published minor release. Upgrade to the latest `artifacty` version before reporting a
7
7
  suspected issue.
8
8
 
9
9
  ## Reporting a Vulnerability
10
10
 
11
11
  Use GitHub private vulnerability reporting if it is enabled for this repository.
12
- If private reporting is unavailable, open a GitHub issue with a minimal summary
13
- and avoid including exploit details, secrets, private artifacts, or sensitive
12
+ If private reporting is unavailable, open a GitHub issue with a brief summary.
13
+ Do not include exploit details, secrets, private artifacts, or sensitive
14
14
  network information.
15
15
 
16
- Please include:
16
+ Include the following information:
17
17
 
18
18
  - Artifacty version and install method.
19
19
  - Node.js version and operating system.
@@ -23,27 +23,36 @@ Please include:
23
23
 
24
24
  ## Security Model
25
25
 
26
- Artifacty is local-first. The default HTTP server binds to `127.0.0.1`; binding
27
- outside loopback requires `ARTIFACTY_SHARE_MODE=lan|team` and an API token.
26
+ Artifacty is local-first. The HTTP server binds to `127.0.0.1` by default.
27
+ To bind outside loopback, set `ARTIFACTY_SHARE_MODE=lan|team` and an API token.
28
28
 
29
29
  The project treats artifact content as untrusted:
30
30
 
31
31
  - HTML, SVG, Mermaid, and React rendering use sandboxed frames and scoped CSP.
32
32
  - React execution is disabled unless `ARTIFACTY_ENABLE_REACT_RENDERER=true`.
33
- - Mutating browser routes reject non-local `Origin` headers.
33
+ - Mutating browser routes reject cross-origin writes. They allow loopback and
34
+ same-origin requests.
34
35
  - API token checks use constant-time digest comparison.
35
- - Artifact content is scanned for common API keys and private keys before
36
- storage unless explicitly allowed.
36
+ - Before storage, Artifacty scans content for common API keys and private keys.
37
+ The caller can explicitly allow content that contains these keys.
37
38
 
38
- Artifacty does not provide TLS termination, remote OAuth, multi-user access
39
- control, or a hosted sync service. Do not expose the server directly to the
40
- public internet.
39
+ Administrators can select public or private site access. Private mode requires
40
+ a personal account and limits artifact access to owners, administrators, and
41
+ users with direct or team grants. Grants permit reading, commenting, or editing.
42
+ Token scopes further restrict API and MCP actions.
43
+
44
+ Administrators can also restrict HTTP access by IP address or CIDR range.
45
+ Artifacty checks the TCP peer address and does not trust forwarding headers.
46
+ Public share links require explicit administrator opt-in in private mode.
47
+
48
+ Artifacty does not provide TLS termination, remote OAuth, or a hosted sync
49
+ service. Do not expose the server directly to the public internet.
41
50
 
42
51
  ## Supply Chain
43
52
 
44
- npm releases are published through GitHub Actions using npm Trusted Publishing
45
- OIDC. The release workflow runs lint, tests, and smoke checks on supported Node
46
- versions before publishing.
53
+ GitHub Actions publishes npm releases through npm Trusted Publishing OIDC.
54
+ Before publication, the release workflow runs lint, tests, and smoke checks on
55
+ supported Node versions.
47
56
 
48
57
  See [docs/threat-model.md](docs/threat-model.md) and
49
58
  [docs/network-sharing.md](docs/network-sharing.md) for operational guidance.