diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..bb87cef --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + open-pull-requests-limit: 5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a4ce8dc --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run check diff --git a/.gitignore b/.gitignore index 70f2973..efb46e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .lakebed/ .env.lakebed.server +.env.lakebed.server.tmp-* lakebed.json node_modules/ +codex-artifacts-backup*.json diff --git a/AGENTS.md b/AGENTS.md index 28ab802..7e28c53 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,10 +3,10 @@ This repository is a Lakebed v0 capsule. Keep application code within `server/`, `client/`, and `shared/`; do not add runtime npm dependencies or Node built-ins to capsule code. - Run `npm run build` after changes. -- Keep authorization server-side. Owner identities are the verified Google emails in `OWNER_EMAILS` within `shared/config.ts`. +- Keep authorization server-side. Emails locate pending invitations; durable owner and recipient authorization uses immutable Lakebed user IDs. - New artifacts must remain private until explicit recipient emails are saved. - Only owners may change access. Preserve exact-email, domain, and public settings when replacing or republishing HTML. - Never add `allow-same-origin` to the artifact iframe sandbox. Artifact HTML is untrusted relative to the authenticated shell. -- Keep HTML chunks below Lakebed's 64 KiB value limit and total state within its 1 MiB free capsule limit. +- Keep HTML chunks below Lakebed's 64 KiB value limit, artifact HTML within the 768 KiB application budget, and total state within Lakebed's 1 MiB capsule limit. - Keep `.env.lakebed.server` and publish tokens out of git. - Use `node scripts/publish.mjs ...` when asked to publish an HTML artifact through a configured deployment. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c0b80d4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,30 @@ +# Contributing + +## Development + +Use Node.js 20 or later: + +```sh +npm ci +npm run check +``` + +`npm run check` runs the dependency-free Node test suite and builds the Lakebed capsule. Run it before every commit. + +Application code belongs in `server/`, `client/`, and `shared/`. Capsule code may use Lakebed-provided modules and pure relative imports, but not Node built-ins or arbitrary runtime npm dependencies. Node built-ins are allowed in repository scripts and tests. + +## Security and privacy invariants + +- New artifacts remain private until access is explicitly saved. +- Authorization stays server-side and uses durable Lakebed user IDs after invitation acceptance. +- Only deployment owners may publish, replace, delete, or change access. +- Republishing preserves omitted exact-email, domain, and public settings. +- The artifact iframe must never gain `allow-same-origin`. +- HTML chunks remain below Lakebed's value limit and total state stays below the deployment limit. +- Tests, examples, commits, and issue reports contain no real credentials, private artifact URLs, or personal data. + +## Pull requests + +Keep changes focused and explain user-visible behavior, security impact, schema migration behavior, and verification. Add regression tests for changes to access control, publisher configuration, CLI parsing, chunking, or deployment setup. + +For security issues, follow [SECURITY.md](SECURITY.md) instead of opening a public pull request first. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b2a0be8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 none23 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE 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. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS 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. diff --git a/README.md b/README.md index 876b7bb..c9263d7 100644 --- a/README.md +++ b/README.md @@ -1,116 +1,218 @@ # Codex Artifacts -Codex Artifacts turns self-contained HTML files into shareable web pages with access control. It is designed for reports, dashboards, plans, walkthroughs, and other visual documents created by coding agents. +Codex Artifacts is a small service for publishing self-contained HTML files as private, shareable web pages. It gives coding agents a place to publish reports, dashboards, plans, walkthroughs, and other visual documents, while you control who can view them. -Artifacts are private by default. Owners can share an artifact with individual Google accounts, allow an entire email domain, or make it public. +You run Codex Artifacts in your own Lakebed account. Your deployment has its own data, owners, URL, and publishing token; there is no shared hosted service or credential. -[View the public README demo](https://codex-artifacts.lakebed.app/a/readme-demo) +## Set up your own service -## What you get +You need: -- Google sign-in and verified-email access checks -- Private-by-default artifact publishing -- Per-artifact sharing by email, domain, or public link -- A browser UI for uploading, replacing, downloading, and deleting artifacts -- A shared Codex and Claude Code skill for agent-driven publishing -- A command-line publisher that can update an existing artifact URL -- Sandboxed HTML previews +- Node.js 20 or later +- A Google account +- A free [Lakebed](https://lakebed.dev) account + +### 1. Clone, install, and deploy + +Replace `you@example.com` with the Google email you will use to administer the service: + +```sh +git clone https://github.com/none23/codex-artifacts.git +cd codex-artifacts +npm ci +npm run setup -- --owner you@example.com +``` + +Follow the Lakebed sign-in prompt if one appears. The command creates your deployment, checks that it is healthy, and prints its service URL. + +### 2. Sign in as the owner + +Open the printed service URL and choose **Sign in with Google**. Use the same email you passed to `--owner`. This first sign-in accepts the owner invitation and gives you access to the management interface. + +### 3. Install the agent skill + +Link the included skill for Codex, Claude Code, or both: + +```sh +REPO_DIR="$(pwd)" + +mkdir -p "${CODEX_HOME:-$HOME/.codex}/skills" +ln -sfn "$REPO_DIR/skills/codex-artifacts" \ + "${CODEX_HOME:-$HOME/.codex}/skills/codex-artifacts" + +mkdir -p "$HOME/.claude/skills" +ln -sfn "$REPO_DIR/skills/codex-artifacts" \ + "$HOME/.claude/skills/codex-artifacts" +``` + +Start a new agent session after installing the skill so it is discovered. -## Use it +### 4. Publish your first artifact -Once the service and skill are installed, ask your agent for an artifact: +From any project, ask your agent: ```text Use codex-artifacts to create and publish a visual architecture report for this repository. ``` -Codex can select the skill automatically when the request calls for a shareable visual page. In Claude Code, invoke `/codex-artifacts` explicitly so it does not conflict with Claude's built-in artifact feature. +The agent creates the HTML, publishes it to your deployment, and opens the private result in your browser. New artifacts are visible only to configured owners until you explicitly share them. -You can also publish an existing HTML file from the repository: +You can also publish an existing HTML file directly from the Codex Artifacts repository: ```sh -node scripts/publish.mjs ./report.html \ - --title "Architecture report" +node scripts/publish.mjs ./report.html --title "Architecture report" ``` +Your service is now ready to use. See the [public README demo](https://codex-artifacts.lakebed.app/a/readme-demo) for a view-only example of a published artifact. + +## Using Codex Artifacts + +The service provides: + +- Google sign-in with durable Lakebed identity bindings +- Private-by-default artifact publishing +- Per-artifact invitations by exact email or email domain +- Optional public links +- A browser UI for uploading, replacing, downloading, and deleting artifacts +- A shared Codex and Claude Code skill for agent-driven publishing +- A command-line publisher that can update an existing artifact URL +- Sandboxed HTML previews without `allow-same-origin` + +### Publishing options + Useful options: ```sh ---slug architecture-report # Reuse the same URL on future updates ---share person@example.com # Share with one or more exact emails +--slug architecture-report # Reuse the URL on future updates +--share person@example.com # Replace the exact-email invitation list --public # Allow anyone with the link to view --no-open # Do not open the result in a browser +-- --option-like-name.html # Publish a filename beginning with "-" ``` -Open a published artifact and use **Access** in the top bar to manage people, domains, and public visibility. Email suggestions are remembered locally in that browser. +Omitting `--share` or `--public` while updating preserves existing access. The publisher refuses to combine a process-level URL override with a token loaded from the configuration file; override `ARTIFACTS_URL` and `ARTIFACTS_PUBLISH_TOKEN` together. + +If the skill is copied away from this repository instead of linked, set `CODEX_ARTIFACTS_ENV` to the absolute path of `.env.lakebed.server`. + +## What setup configures + +The setup command: -## Install +1. Creates a random 256-bit publishing token. +2. Writes the ignored `.env.lakebed.server` with mode `0600`. +3. Opens Lakebed developer login if needed. +4. Creates an owned deployment or updates the deployment already bound in `lakebed.json`. +5. Saves the deployment URL and verifies `/api/status`. -You need Node.js, a Google account, and a free [Lakebed](https://lakebed.dev) deployment. +Re-run `npm run setup` after pulling an update. Existing owners, secrets, and a configured custom URL are preserved unless you explicitly replace the owner list with `--owner`. -### 1. Configure and deploy the service +## Identity and access + +`OWNER_EMAILS` and artifact recipient emails are invitations, not permanent authorization keys. + +- The first matching verified Google sign-in accepts an invitation and binds it to the account's immutable Lakebed user ID. +- Later requests authorize the bound user ID rather than trusting current profile email. +- Removing an owner invitation from `OWNER_EMAILS` and redeploying revokes that binding. +- Removing an artifact email or domain rule immediately invalidates and removes grants created from that rule. +- Every configured owner is a deployment administrator and can manage every artifact. + +Domain invitations are broad. Do not add public mail domains such as `gmail.com`; every matching signed-in account could accept access. Prefer exact-email invitations for sensitive artifacts. + +## Manual setup + +The setup command is recommended, but the equivalent manual flow is: ```sh -git clone codex-artifacts -cd codex-artifacts cp .env.lakebed.server.example .env.lakebed.server +chmod 600 .env.lakebed.server ``` -Edit the ignored `.env.lakebed.server`: +Set: ```dotenv -OWNER_EMAILS=you@example.com,another-account@example.com +OWNER_EMAILS=you@example.com PUBLISH_TOKEN=replace-with-a-long-random-secret ARTIFACTS_URL=https://your-artifacts.lakebed.app ``` -Generate a publish token with `openssl rand -hex 32`. Then deploy and claim the app: +Generate the token with `openssl rand -hex 32`. Authenticate before the first deployment so Lakebed creates an owned app: ```sh +npm exec lakebed -- auth login npm run deploy -npx lakebed@0.0.29 auth login -npx lakebed@0.0.29 claim ``` -Set `ARTIFACTS_URL` to the URL Lakebed gives you and deploy once more so local publishing uses the final address: +Set `ARTIFACTS_URL` to the deployed or custom URL. `lakebed.json` is intentionally ignored in this upstream repository so a clone never targets the maintainer's deployment; Lakebed creates your local binding automatically. + +## Configuration + +| Variable | Purpose | +| --- | --- | +| `OWNER_EMAILS` | Comma-separated pending/current owner invitations | +| `PUBLISH_TOKEN` | Server-side automation secret | +| `ARTIFACTS_URL` | Publisher destination in the local configuration file | +| `ARTIFACTS_PUBLISH_TOKEN` | Process-level publisher token override | +| `ARTIFACTS_AUTO_OPEN=0` | Disables opening newly published artifacts | +| `CODEX_ARTIFACTS_ENV` | Optional absolute publisher environment-file path | +| `LAKEBED_TOKEN` | Optional Lakebed deployment credential for automation | + +Never commit `.env.lakebed.server`, `.lakebed/`, `lakebed.json`, or publishing/deployment tokens. + +The owner automation contract is documented in [openapi.yaml](openapi.yaml). + +## Operations + +### Update safely ```sh -npm run deploy +git pull --ff-only +npm run check +npm run setup ``` -### 2. Install the agent skill +`npm run setup` updates the bound deployment only after tests/build are run separately. For a controlled rollback, check out the last known-good revision, run `npm run check`, then `npm run deploy`. + +### Back up data -Link the same skill directory for Codex, Claude Code, or both: +Read the deploy ID from the ignored `lakebed.json`, then export: ```sh -REPO_DIR="$(pwd)" +DEPLOY_ID="$(node -p "JSON.parse(require('fs').readFileSync('lakebed.json')).deployId")" +npm exec lakebed -- db export "$DEPLOY_ID" --out codex-artifacts-backup.json +``` -mkdir -p "${CODEX_HOME:-$HOME/.codex}/skills" -ln -sfn "$REPO_DIR/skills/codex-artifacts" \ - "${CODEX_HOME:-$HOME/.codex}/skills/codex-artifacts" +Lakebed export is not a point-in-time snapshot during concurrent writes. Keep backups private: they contain artifact HTML, owner invitations, and recipient access data. -mkdir -p "$HOME/.claude/skills" -ln -sfn "$REPO_DIR/skills/codex-artifacts" \ - "$HOME/.claude/skills/codex-artifacts" -``` +### Rotate the publisher token -If your skill directory differs, set `CODEX_ARTIFACTS_SKILL_DIR` to the installed skill path. If the skill is copied away from the repository, set `CODEX_ARTIFACTS_ENV` to the absolute path of your `.env.lakebed.server`. +Replace `PUBLISH_TOKEN` in `.env.lakebed.server` with a new 64-character hex value, keep the file at mode `0600`, and run `npm run deploy`. The publisher reads the same local file, so the old token stops working after deployment. -## Configuration +### Change owners -| Variable | Purpose | -| --- | --- | -| `OWNER_EMAILS` | Comma-separated Google accounts that can manage every artifact | -| `PUBLISH_TOKEN` | Server secret accepted by the automation endpoint | -| `ARTIFACTS_URL` | Public base URL used by the publishing script | -| `ARTIFACTS_PUBLISH_TOKEN` | Optional local override for `PUBLISH_TOKEN` | -| `ARTIFACTS_AUTO_OPEN=0` | Disables opening newly published artifacts | -| `CODEX_ARTIFACTS_ENV` | Optional path to the publisher environment file | +Update `OWNER_EMAILS` and run `npm run deploy`. Removing an email revokes its bound owner access. Adding an email creates a pending invitation that binds on that person's next sign-in. + +## Security and capacity + +Artifact HTML is untrusted. It runs in an iframe sandbox without `allow-same-origin` and cannot access the authenticated shell, but scripts, forms, popups, and outbound network requests are currently allowed inside the artifact. An artifact can transmit data embedded in its own HTML. Downloaded HTML is no longer sandboxed if you open it directly. Do not publish secrets, credentials, private source, or regulated data without reviewing the generated page. + +The publishing token is deployment-wide owner automation authority. Anyone holding it can create artifacts and replace an artifact whose slug they know. Keep it only on trusted owner machines; do not distribute it as a consumer credential. + +Lakebed currently limits capsule state to 1 MiB. This project limits one artifact to 512 KiB, individual chunks to 48 KiB, and total artifact HTML to 768 KiB, reserving the remaining state for metadata, access grants, and indexes. Treat the deployment as a small visual-document workspace, not general hosting. Delete superseded artifacts and monitor usage with Lakebed inspection tools. + +Public artifacts are subject to the [Lakebed Acceptable Use Policy](https://lakebed.dev/acceptable-use). The deployment owner is responsible for its published content and recipients. + +## Local development + +```sh +npm test +npm run build +npm run dev +``` -Never commit `.env.lakebed.server`, `lakebed.json`, or a publish token. +Lakebed local state resets when the dev process restarts. Real Google sign-in accepts configured owner and artifact invitations; automation publishing continues to use `PUBLISH_TOKEN`. ## How it works -The project is a small Lakebed capsule. Lakebed supplies Google authentication, storage, and hosting. Artifact HTML is split into database-safe chunks and rendered in a sandboxed iframe without `allow-same-origin`, keeping it isolated from the authenticated application. +The project is a Lakebed v0 capsule. Lakebed supplies first-party Google authentication, transactional storage, and hosting. Artifact HTML is split into database-safe chunks. Owner and recipient invitations bind to durable Lakebed user IDs on first matching sign-in. HTML is rendered with `srcDoc` in a sandboxed iframe without `allow-same-origin`. -The current 512 KiB artifact limit makes this a good fit for self-contained reports and visual documents rather than a general file-hosting service. +Codex Artifacts is an independent project and is not affiliated with or endorsed by OpenAI or Anthropic. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..84b4860 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,22 @@ +# Security policy + +## Supported version + +Security fixes are made on the default branch. This project has not yet published a stable compatibility or long-term-support policy. + +## Report a vulnerability + +Use **Security → Report a vulnerability** in the GitHub repository so details remain private. If private vulnerability reporting is unavailable, open a minimal issue asking the maintainer to establish a private channel; do not include exploit details, credentials, private artifact URLs, or personal data in a public issue. + +Include: + +- Affected revision and deployment mode +- Reproduction steps or a proof of concept +- Expected and observed impact +- Any suggested mitigation + +Do not test against a deployment you do not own or have explicit permission to assess. + +## Deployment secrets + +Publishing tokens, Lakebed tokens, `.env.lakebed.server`, `.lakebed/`, database exports, and private artifact URLs must not be committed or included in reports. Rotate a token immediately if it is exposed. diff --git a/client/index.tsx b/client/index.tsx index b967d8f..49de515 100644 --- a/client/index.tsx +++ b/client/index.tsx @@ -10,10 +10,11 @@ import { useLocation, useParams } from "lakebed/client"; -import { useMemo, useState } from "preact/hooks"; +import { useEffect, useMemo, useState } from "preact/hooks"; import type app from "../server"; import { MAX_ARTIFACT_BYTES, + MAX_TOTAL_ARTIFACT_BYTES, chunkHtml, cleanSlug, isValidDomain, @@ -153,7 +154,7 @@ function NewArtifactForm() {

New artifact

Publish an HTML file

-

Only you can see it until you add verified Google emails. Maximum {formatBytes(MAX_ARTIFACT_BYTES)}.

+

Only you can see it until you add recipients. Maximum {formatBytes(MAX_ARTIFACT_BYTES)} per artifact; {formatBytes(MAX_TOTAL_ARTIFACT_BYTES)} workspace HTML budget.

void submit(event)}>