@charlescms/astro 0.1.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/LICENSE +21 -0
- package/README.md +366 -0
- package/SECURITY.md +77 -0
- package/THIRD_PARTY_NOTICES.md +56 -0
- package/connector/worker.js +505 -0
- package/connector/wrangler.toml +15 -0
- package/package.json +92 -0
- package/scripts/check-licenses.js +45 -0
- package/scripts/check-package.js +62 -0
- package/scripts/setup.js +719 -0
- package/scripts/update-vendored-site.js +71 -0
- package/src/admin.astro +314 -0
- package/src/analyzer.js +639 -0
- package/src/asset-images.js +130 -0
- package/src/astro-frontmatter.js +17 -0
- package/src/boot.js +35 -0
- package/src/client.js +347 -0
- package/src/connector-client.js +185 -0
- package/src/content-bridge.js +162 -0
- package/src/content-panel.js +440 -0
- package/src/data-analyzer.js +304 -0
- package/src/edit-affordance.js +463 -0
- package/src/editor-styles.js +243 -0
- package/src/element-editor.js +355 -0
- package/src/fields.js +6 -0
- package/src/frontmatter.js +153 -0
- package/src/ids.js +20 -0
- package/src/index.js +681 -0
- package/src/js-ast.js +140 -0
- package/src/markdown-analyzer.js +95 -0
- package/src/media-preview.js +58 -0
- package/src/panel-manager.js +133 -0
- package/src/publishing.js +457 -0
- package/src/rich-text-editor.js +209 -0
- package/src/routes.js +21 -0
- package/src/runtime-controller.js +206 -0
- package/src/sanitize.js +150 -0
- package/src/section-editor.js +437 -0
- package/src/source-edit.js +310 -0
- package/src/source-map-runtime.js +184 -0
- package/src/staged-panel.js +145 -0
- package/src/toolbar.js +128 -0
- package/src/versions-panel.js +112 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright © 2026 Charles CMS
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# @charlescms/astro
|
|
2
|
+
|
|
3
|
+
Static-first visual editing for Astro sites. Free and open source (MIT).
|
|
4
|
+
|
|
5
|
+
> ⚠️ **Early days (0.x).** CharlesCMS is young and under active development — some
|
|
6
|
+
> things may be rough or not work on every site yet. Source edits are byte-verified
|
|
7
|
+
> and conservative by design, but please **test on a branch first** and
|
|
8
|
+
> [report anything that breaks](https://github.com/Charles-CMS/astro/issues).
|
|
9
|
+
> APIs may change before 1.0.
|
|
10
|
+
|
|
11
|
+
## Quickstart (≈2 minutes)
|
|
12
|
+
|
|
13
|
+
Requires Node.js 22.12 or newer and Astro 6.2.
|
|
14
|
+
|
|
15
|
+
Local preview needs no service account. To publish edits, you only need a GitHub
|
|
16
|
+
repository and a Cloudflare account; the connector is designed to fit within the
|
|
17
|
+
Cloudflare Workers free tier for typical small-site use.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @charlescms/astro
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Astro 6 currently permits an `esbuild` release affected by June 2026 security
|
|
24
|
+
advisories. Until Astro/Vite require the patched release themselves, pin it in
|
|
25
|
+
the consuming site's root `package.json`, reinstall, and verify with
|
|
26
|
+
`npm audit --omit=dev`:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"overrides": {
|
|
31
|
+
"esbuild": "^0.28.1"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
// astro.config.mjs
|
|
38
|
+
import { defineConfig } from "astro/config";
|
|
39
|
+
import charlesCMS from "@charlescms/astro";
|
|
40
|
+
|
|
41
|
+
export default defineConfig({ integrations: [charlesCMS()] });
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm run dev
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Open **`/cms`**, then click any text or image on the page to edit it. That's the
|
|
49
|
+
whole setup — no schema, no content models, no config.
|
|
50
|
+
|
|
51
|
+
Edits are a local preview until you connect the connector (below) — then
|
|
52
|
+
**Publish** commits them straight to your Git repo.
|
|
53
|
+
|
|
54
|
+
## Publish your edits
|
|
55
|
+
|
|
56
|
+
To go live, deploy the connector — a small Cloudflare Worker that commits edits to
|
|
57
|
+
GitHub. One command walks you through it (GitHub repo, where you'll edit, Worker
|
|
58
|
+
name, branch, and an editor password):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx charlescms setup --deploy
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Then **connect the site to it once on `/cms`** — paste the connector URL and the
|
|
65
|
+
browser remembers it. No config editing needed.
|
|
66
|
+
|
|
67
|
+
> **Optional:** to skip even that form, bake the connection into `astro.config` so
|
|
68
|
+
> editors land straight on **Start editing**:
|
|
69
|
+
>
|
|
70
|
+
> ```js
|
|
71
|
+
> charlesCMS({
|
|
72
|
+
> connector: "https://<your-worker>.workers.dev",
|
|
73
|
+
> repo: "owner/name",
|
|
74
|
+
> branch: "main",
|
|
75
|
+
> })
|
|
76
|
+
> ```
|
|
77
|
+
|
|
78
|
+
Verify anytime:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npx charlescms doctor https://<your-worker>.workers.dev
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
No Wrangler install needed — `npx` fetches it. Prefer clicking or no CLI? See
|
|
85
|
+
[Manual setup](#manual-setup).
|
|
86
|
+
|
|
87
|
+
### CLI reference
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npx charlescms setup [--deploy] [--open] # scaffold the connector (and deploy)
|
|
91
|
+
npx charlescms doctor [connector-url] # check files + a live connector verify
|
|
92
|
+
npx charlescms --help # all commands and options
|
|
93
|
+
npx charlescms --version
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`setup` detects your repository's default branch automatically, so edits target
|
|
97
|
+
the right branch (`main`, `master`, …) without you having to pick. `--open` opens
|
|
98
|
+
the GitHub App page for you; `--deploy` also stores the Worker secrets and runs
|
|
99
|
+
`wrangler deploy`.
|
|
100
|
+
|
|
101
|
+
## Deploy your site
|
|
102
|
+
|
|
103
|
+
**Publish** commits to GitHub, so any host that rebuilds on push works — Cloudflare
|
|
104
|
+
Pages, Netlify, Vercel, GitHub Pages, or your own CI.
|
|
105
|
+
|
|
106
|
+
**Cloudflare keeps it simplest.** The connector already runs on Cloudflare Workers,
|
|
107
|
+
so hosting the site on **Cloudflare Pages** keeps everything on one account and the
|
|
108
|
+
same `wrangler` CLI. Connect the repo in the Cloudflare dashboard — build command
|
|
109
|
+
`npm run build`, output directory `dist` — or push straight from the CLI:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npm run build
|
|
113
|
+
npx wrangler pages deploy dist
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Each **Publish** commits to GitHub; Pages rebuilds automatically. Edit → Publish →
|
|
117
|
+
live.
|
|
118
|
+
|
|
119
|
+
> The site is a standard static Astro build (`npm run build` → `dist/`), so any
|
|
120
|
+
> static host works — only the connector requires Cloudflare Workers. When you host
|
|
121
|
+
> elsewhere, add that site's URL to the connector's `ALLOWED_ORIGINS`.
|
|
122
|
+
|
|
123
|
+
## Editor password
|
|
124
|
+
|
|
125
|
+
Protect a public editor with one password — the Worker secret `AUTH_SECRET`.
|
|
126
|
+
Editors enter it once on `/cms`.
|
|
127
|
+
|
|
128
|
+
- **Set / change:** in the assistant, `npx wrangler secret put AUTH_SECRET`, or the
|
|
129
|
+
Cloudflare dashboard (Settings → Variables and Secrets). Takes effect immediately.
|
|
130
|
+
- **Rotate:** replace it with `npx wrangler secret put AUTH_SECRET`.
|
|
131
|
+
|
|
132
|
+
The connector **fails closed**: with no `AUTH_SECRET` set it refuses every edit
|
|
133
|
+
(503), so a misconfigured deploy can never publish openly — always set one. The
|
|
134
|
+
secret is sent as a header over HTTPS and compared in constant time; GitHub
|
|
135
|
+
credentials stay in the Worker (never in the site or `localStorage`).
|
|
136
|
+
|
|
137
|
+
It's a single shared editor identity (no per-user audit; revoke by rotating). For
|
|
138
|
+
teams / per-user auth on higher-value sites, put the Worker behind **Cloudflare
|
|
139
|
+
Access**, or add **GitHub OAuth** in the `authorizeRequest()` hook
|
|
140
|
+
(`connector/worker.js`), plus a Cloudflare rate-limit rule on `/api/*`.
|
|
141
|
+
|
|
142
|
+
## Add sections (optional)
|
|
143
|
+
|
|
144
|
+
By default the editor changes existing content in place. **Optionally**, let editors
|
|
145
|
+
insert whole new sections between developer-approved children. Mark the wrapper:
|
|
146
|
+
|
|
147
|
+
```astro
|
|
148
|
+
<main
|
|
149
|
+
data-charlescms-sections
|
|
150
|
+
data-charlescms-section-class="prose section-shell"
|
|
151
|
+
data-charlescms-section-tools="header,paragraph,list,image,quote,delimiter"
|
|
152
|
+
>
|
|
153
|
+
<Hero />
|
|
154
|
+
<Features />
|
|
155
|
+
<Testimonials />
|
|
156
|
+
</main>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
That one attribute is the whole decision: sections can be inserted **exactly where
|
|
160
|
+
the developer placed the wrapper**, and nowhere else — pages without it never show
|
|
161
|
+
an insert control. The wrapper's direct children must be static markup (dynamic
|
|
162
|
+
`.map()` children are skipped).
|
|
163
|
+
|
|
164
|
+
- `data-charlescms-section-class` — class(es) every inserted section receives, so
|
|
165
|
+
new sections inherit the site's own spacing and look.
|
|
166
|
+
- `data-charlescms-section-tools` — which blocks editors may use
|
|
167
|
+
(`header,paragraph,list,image,quote,delimiter`).
|
|
168
|
+
|
|
169
|
+
Offer branded templates as static snippets in `src/charlescms/templates/`
|
|
170
|
+
(recommended — you own the markup and classes). Every `.html` or `.astro` file
|
|
171
|
+
there becomes a choice in the picker; the filename is the label
|
|
172
|
+
(`team-quote.html` → “Team Quote”):
|
|
173
|
+
|
|
174
|
+
```html
|
|
175
|
+
<!-- src/charlescms/templates/newsletter.html -->
|
|
176
|
+
<h2 class="display-lg">Stay in the loop</h2>
|
|
177
|
+
<p>Occasional notes from the studio.</p>
|
|
178
|
+
<a class="button" href="/newsletter">Subscribe</a>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Templates must be pure static markup: files containing scripts, frontmatter,
|
|
182
|
+
`{expressions}` or components are skipped with a dev-server warning (a section is
|
|
183
|
+
inserted verbatim as page content, so nothing dynamic can ride along).
|
|
184
|
+
|
|
185
|
+
**What the editor experiences:**
|
|
186
|
+
|
|
187
|
+
1. **Add section here** appears before, between, and after the wrapper's sections.
|
|
188
|
+
2. Clicking it offers your templates; choosing one stages the section as a live
|
|
189
|
+
draft on the page. Clicking the draft offers **Remove section** — nothing
|
|
190
|
+
touches the repository until **Publish**.
|
|
191
|
+
3. Publish commits the section as sanitized, static Astro source (wrapped in a
|
|
192
|
+
durable `data-charlescms-block`). From then on it is ordinary page content:
|
|
193
|
+
its text and images are edited right on the page, like everything else, and
|
|
194
|
+
the whole section can be removed again at any time.
|
|
195
|
+
|
|
196
|
+
There is deliberately no free-form block builder: editors compose pages from
|
|
197
|
+
sections you designed, and edit content in one consistent way — on the page.
|
|
198
|
+
|
|
199
|
+
## What it can edit
|
|
200
|
+
|
|
201
|
+
Everything visible is edited by clicking it on the page. **Page info** in the
|
|
202
|
+
toolbar holds the fields that render nowhere visible — the Google/browser-tab
|
|
203
|
+
title, the meta description, and other frontmatter.
|
|
204
|
+
|
|
205
|
+
- **Static text & headings**, and safe inline **rich text** (bold, italic, inline
|
|
206
|
+
code, links, line breaks)
|
|
207
|
+
- **Markdown** frontmatter and body — incl. Astro **Content Collections** (`.md`,
|
|
208
|
+
`.mdx`, and **JSON** `type: 'data'` collections)
|
|
209
|
+
- **Data rendered with `.map()`** — arrays of objects *and* positional tuples
|
|
210
|
+
(`[["Name","Desc","8"], …]`): names, descriptions, prices, list items
|
|
211
|
+
- **Config values** (`site.phone`, address, hours, …) and **component props**
|
|
212
|
+
(hero titles, section eyebrows) — clickable on the page, edited safely at source
|
|
213
|
+
- **Navigation menus** — labels edit; destinations stay locked
|
|
214
|
+
- **Links & downloads** — link text edits; PDFs/files replace in place
|
|
215
|
+
- **Images, video, audio, embeds, iframes** — upload, swap, bounded remove
|
|
216
|
+
- **New sections** (see above) and **versions / undo** via Git commits
|
|
217
|
+
|
|
218
|
+
Rich vs plain is decided by how your site renders a value: where it interprets
|
|
219
|
+
formatting (a Markdown body, `set:html`), you get full rich text; where it prints
|
|
220
|
+
raw text (`{value}`), it stays plain so formatting can never show as literal
|
|
221
|
+
characters. Pure logic — loops, conditions, computed expressions — is never
|
|
222
|
+
editable (that's code, not content); clicking such generated content shows a
|
|
223
|
+
calm "generated by the page, can't be edited here" hint instead of doing nothing.
|
|
224
|
+
Every write is verified against the exact source bytes and refused if the source
|
|
225
|
+
changed, so an edit can't silently corrupt surrounding code. Edited values are
|
|
226
|
+
escaped as content — including the `{`/`}` Astro reads as expressions — so typed
|
|
227
|
+
text stays text and can't be reinterpreted as code; a downstream `astro build`
|
|
228
|
+
remains the final check on any committed file.
|
|
229
|
+
|
|
230
|
+
CharlesCMS scans your source to know what's editable — automatically on save in
|
|
231
|
+
dev, at build time in production (rebuild to pick up new content). It works on any
|
|
232
|
+
Astro site, with or without a layout, alongside React/Svelte/Vue islands (those
|
|
233
|
+
files are never touched).
|
|
234
|
+
|
|
235
|
+
## Options
|
|
236
|
+
|
|
237
|
+
```js
|
|
238
|
+
charlesCMS({
|
|
239
|
+
// Connection (optional — bake in for one-click "Start editing"):
|
|
240
|
+
connector: "https://<your-worker>.workers.dev",
|
|
241
|
+
repo: "owner/name",
|
|
242
|
+
branch: "main",
|
|
243
|
+
sourceRoot: "website", // monorepos only
|
|
244
|
+
|
|
245
|
+
editablePaths: ["/demo", "/blog"], // limit where the editor activates
|
|
246
|
+
previewOnly: false, // true = local preview only, no publishing
|
|
247
|
+
adminPath: "/cms", // change the editor route
|
|
248
|
+
})
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Monorepos: `sourceRoot` is the app folder holding the Astro project, so
|
|
252
|
+
`src/pages/index.astro` commits as `website/src/pages/index.astro`.
|
|
253
|
+
|
|
254
|
+
## Manual setup
|
|
255
|
+
|
|
256
|
+
Skip the assistant and set up the Worker by hand:
|
|
257
|
+
|
|
258
|
+
1. Install + configure as in [Quickstart](#quickstart).
|
|
259
|
+
|
|
260
|
+
2. Create the Worker:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
mkdir -p connector
|
|
264
|
+
cp node_modules/@charlescms/astro/connector/worker.js connector/worker.js
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
```toml
|
|
268
|
+
# connector/wrangler.toml
|
|
269
|
+
name = "<your-worker>"
|
|
270
|
+
main = "./worker.js"
|
|
271
|
+
compatibility_date = "2026-06-04"
|
|
272
|
+
|
|
273
|
+
[vars]
|
|
274
|
+
ALLOWED_ORIGINS = "https://www.yoursite.com" # comma-separated; add http://localhost:4321 for dev
|
|
275
|
+
ALLOWED_REPOS = "owner/name"
|
|
276
|
+
DEFAULT_BRANCH = "main"
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
cd connector && npx wrangler login
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
3. Store secrets (GitHub App with `contents: write` + `metadata: read`, installed on
|
|
284
|
+
the repo) and the editor password:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
npx wrangler secret put GITHUB_APP_ID
|
|
288
|
+
npx wrangler secret put GITHUB_PRIVATE_KEY
|
|
289
|
+
npx wrangler secret put AUTH_SECRET # required — the connector refuses to act without it
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
4. Deploy and verify:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
npx wrangler deploy
|
|
296
|
+
npx charlescms doctor https://<your-worker>.workers.dev
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Troubleshooting
|
|
300
|
+
|
|
301
|
+
`npx charlescms doctor <url>` maps each failure to a fix:
|
|
302
|
+
|
|
303
|
+
| Symptom | Likely cause | Fix |
|
|
304
|
+
| --- | --- | --- |
|
|
305
|
+
| `401` on publish or in `doctor` | Missing/wrong editor password | Use the value matching the Worker's `AUTH_SECRET` (or set it with `wrangler secret put AUTH_SECRET`) |
|
|
306
|
+
| `403` "not allowed" | Origin/repo not allow-listed | Fix `ALLOWED_ORIGINS` / `ALLOWED_REPOS` in `wrangler.toml`, redeploy |
|
|
307
|
+
| `403` from GitHub | App lacks access, or stale Worker | Install/grant the GitHub App, then `wrangler deploy` |
|
|
308
|
+
| `404` | GitHub App not installed on the repo | Install it (doctor prints the exact `github.com/apps/<app>/installations/new` link), or check the repo path |
|
|
309
|
+
|
|
310
|
+
## How it works & safety
|
|
311
|
+
|
|
312
|
+
The editor ships as static HTML/JS, reads a build-time source map, and stages edits
|
|
313
|
+
as a live preview. Publish sends the exact source span to the Worker, which commits
|
|
314
|
+
to GitHub — credentials stay in the Worker, never in the site or `localStorage`.
|
|
315
|
+
Before each commit the file is re-read and the edit refused if the source changed;
|
|
316
|
+
rich text and section HTML are re-sanitized before the authenticated connector
|
|
317
|
+
writes the complete updated file.
|
|
318
|
+
|
|
319
|
+
## Checks
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
npm test
|
|
323
|
+
npm run test:coverage
|
|
324
|
+
npm run test:e2e
|
|
325
|
+
npm run build
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
GitHub Actions runs the coverage and browser suites on Linux plus the packed
|
|
329
|
+
package smoke test on `ubuntu-latest`, `macos-latest`, and `windows-latest`.
|
|
330
|
+
Each job installs the tarball in a clean Astro project, runs the CLI setup,
|
|
331
|
+
builds, starts the dev server, and checks both `/` and `/cms`.
|
|
332
|
+
|
|
333
|
+
## Runtime structure
|
|
334
|
+
|
|
335
|
+
The browser editor is split by responsibility:
|
|
336
|
+
|
|
337
|
+
- `src/client.js` is the composition root: shared state, module wiring, session
|
|
338
|
+
helpers, and the direct Tiptap imports required for reliable Vite
|
|
339
|
+
`file:` installs.
|
|
340
|
+
- `src/runtime-controller.js`, `src/source-map-runtime.js`, and
|
|
341
|
+
`src/edit-affordance.js` own startup, source-map binding, and click/hover
|
|
342
|
+
behavior.
|
|
343
|
+
- `src/element-editor.js`, `src/rich-text-editor.js`, and
|
|
344
|
+
`src/section-editor.js` own the three editing experiences.
|
|
345
|
+
- `src/publishing.js` owns staging, publishing, uploads, replacements, and
|
|
346
|
+
bounded media removal.
|
|
347
|
+
- `src/content-panel.js` owns the Page info (frontmatter/SEO) and data forms;
|
|
348
|
+
`src/content-bridge.js` maps rendered data to those deterministic forms.
|
|
349
|
+
- `src/panel-manager.js`, `src/toolbar.js`, `src/versions-panel.js`, and
|
|
350
|
+
`src/editor-styles.js` own the surrounding editor UI.
|
|
351
|
+
- `src/analyzer.js` handles Astro markup while `src/data-analyzer.js` handles
|
|
352
|
+
conservative JavaScript/TypeScript/JSON data collections. Both read source
|
|
353
|
+
through `src/js-ast.js`, a small layer over the Babel parser that locates
|
|
354
|
+
string literals at exact offsets (any nesting depth) without evaluating code.
|
|
355
|
+
|
|
356
|
+
All source writes remain byte-verified in the shared source-editing layer; the
|
|
357
|
+
DOM bridge only chooses which deterministic editor to open.
|
|
358
|
+
|
|
359
|
+
## License
|
|
360
|
+
|
|
361
|
+
CharlesCMS is free and open source under the [MIT License](./LICENSE) — use it
|
|
362
|
+
for anything, including commercial work, no fees and no keys.
|
|
363
|
+
Third-party dependency licences are summarized in
|
|
364
|
+
[THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md).
|
|
365
|
+
|
|
366
|
+
Everything self-hosts on your own Cloudflare and GitHub.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
How CharlesCMS is built to be safe to install on a real site, what it deliberately
|
|
4
|
+
does and does **not** protect, and how to report a problem.
|
|
5
|
+
|
|
6
|
+
## Reporting a vulnerability
|
|
7
|
+
|
|
8
|
+
Please do **not** open a public issue for security problems. Report them privately
|
|
9
|
+
through [GitHub's private vulnerability reporting form](https://github.com/Charles-CMS/astro/security/advisories/new).
|
|
10
|
+
Enable private vulnerability reporting in the repository settings before the
|
|
11
|
+
first public release.
|
|
12
|
+
|
|
13
|
+
## Trust model in one paragraph
|
|
14
|
+
|
|
15
|
+
The published site is **100 % static** — none of the editor runs for visitors. The
|
|
16
|
+
editor boots only on the admin route (`/cms` by default) and only does anything once
|
|
17
|
+
an editor authenticates. All writes go through the **connector** (a Cloudflare
|
|
18
|
+
Worker you host), which is the only server-side component and the only thing holding
|
|
19
|
+
credentials.
|
|
20
|
+
|
|
21
|
+
## Connector — the only component with secrets
|
|
22
|
+
|
|
23
|
+
- **GitHub App installation tokens**, not a personal access token: short-lived
|
|
24
|
+
(~1 h), scoped to the installed repo, cached in memory. The token is never sent to
|
|
25
|
+
the browser and never committed.
|
|
26
|
+
- **Editor auth**: a shared secret `AUTH_SECRET`, sent as the `x-charlescms-auth`
|
|
27
|
+
header and compared in **constant time** (both sides hashed to SHA-256, then a
|
|
28
|
+
branchless XOR) so neither length nor mismatch position leaks. **Fail-closed**:
|
|
29
|
+
with no `AUTH_SECRET` set, every action is refused (503) — a misconfigured deploy
|
|
30
|
+
can never publish openly.
|
|
31
|
+
- **Origin allow-list** and **repo allow-list** — requests from other origins or for
|
|
32
|
+
other repositories are rejected (403). The origin check is browser-facing CSRF
|
|
33
|
+
defense, not the access boundary: browsers always attach an `Origin` a malicious
|
|
34
|
+
site can't forge, but a non-browser client can omit it. Access is gated by
|
|
35
|
+
`AUTH_SECRET` (above) and the repo allow-list, which apply to every request.
|
|
36
|
+
- **Path traversal** is blocked everywhere (no `..`, no absolute paths).
|
|
37
|
+
- Source writes require an existing Git blob SHA and are limited to supported
|
|
38
|
+
files below a `src` directory. New binary files may only be created below
|
|
39
|
+
`public/uploads`; existing public assets require their current SHA.
|
|
40
|
+
- Recommended hardening before selling: a **Cloudflare rate-limit rule on `/api/*`**
|
|
41
|
+
to blunt password guessing; for multiple editors, put the Worker behind
|
|
42
|
+
**Cloudflare Access** and verify the access JWT instead of a single shared secret.
|
|
43
|
+
|
|
44
|
+
## Source-edit safety and trust boundary
|
|
45
|
+
|
|
46
|
+
- Every edit is applied **byte-exact** against a build-time source map. Before
|
|
47
|
+
writing, the browser re-reads the file through the connector and **refuses** if
|
|
48
|
+
the recorded bytes no longer match (the source drifted).
|
|
49
|
+
- Rich text and Markdown are re-parsed and sanitized in the editor before the
|
|
50
|
+
complete updated source is sent to the connector.
|
|
51
|
+
- The connector restricts repository paths and requires GitHub's current blob SHA,
|
|
52
|
+
so stale writes and writes to workflows or other repository metadata are rejected.
|
|
53
|
+
|
|
54
|
+
The authenticated editor is still a trusted publisher. Anyone who obtains
|
|
55
|
+
`AUTH_SECRET`, or gains script execution in an authenticated editor tab, can submit
|
|
56
|
+
changes to allowed source files. Those files can contain executable Astro or
|
|
57
|
+
JavaScript code. Protect the secret, prevent untrusted scripts on the editor origin,
|
|
58
|
+
keep the GitHub App scoped to dedicated repositories, and use branch protection or
|
|
59
|
+
a review branch for higher-value sites. A downstream `astro build` detects syntax
|
|
60
|
+
errors; it is not a malware sandbox.
|
|
61
|
+
|
|
62
|
+
## Supply-chain integrity (open, not obfuscated)
|
|
63
|
+
|
|
64
|
+
The package is **open source and shipped readable** — deliberately not
|
|
65
|
+
obfuscated. That lets anyone (or any scanner) audit exactly what runs, which matters
|
|
66
|
+
because the connector has write access to your repo. To prove the published npm
|
|
67
|
+
package is built from this public source and nothing was slipped in, releases are
|
|
68
|
+
published from CI with **[npm provenance](https://docs.npmjs.com/generating-provenance-statements)**
|
|
69
|
+
(`publishConfig.provenance`): a Sigstore-signed attestation linking the tarball to
|
|
70
|
+
the exact commit and build. Look for the **Provenance** badge on the npm page.
|
|
71
|
+
|
|
72
|
+
## Development-only endpoints
|
|
73
|
+
|
|
74
|
+
In `astro dev` the integration exposes `/_charlescms/mirror-*` endpoints that write
|
|
75
|
+
uploaded media and just-published source back to the **local working tree only**, so
|
|
76
|
+
the dev preview stays in sync. They are restricted to the project directory (no
|
|
77
|
+
traversal, source-file types only) and **do not exist in a production build**.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Third-party software
|
|
2
|
+
|
|
3
|
+
CharlesCMS depends on third-party software. The npm package does not bundle its
|
|
4
|
+
dependencies; npm installs them as separate packages with their own licence files.
|
|
5
|
+
An Astro production build may bundle browser-side portions of these packages.
|
|
6
|
+
|
|
7
|
+
## Direct runtime dependencies
|
|
8
|
+
|
|
9
|
+
The following direct dependencies are licensed under the MIT License:
|
|
10
|
+
|
|
11
|
+
- `@astrojs/compiler`
|
|
12
|
+
- `@babel/parser`
|
|
13
|
+
- `@tiptap/core`
|
|
14
|
+
- `@tiptap/extension-bold`
|
|
15
|
+
- `@tiptap/extension-code`
|
|
16
|
+
- `@tiptap/extension-document`
|
|
17
|
+
- `@tiptap/extension-hard-break`
|
|
18
|
+
- `@tiptap/extension-italic`
|
|
19
|
+
- `@tiptap/extension-link`
|
|
20
|
+
- `@tiptap/extension-text`
|
|
21
|
+
- `@tiptap/pm`
|
|
22
|
+
- `js-yaml`
|
|
23
|
+
- `mdast-util-from-markdown`
|
|
24
|
+
- `mdast-util-gfm`
|
|
25
|
+
- `mdast-util-to-markdown`
|
|
26
|
+
- `mdast-util-to-string`
|
|
27
|
+
- `micromark-extension-gfm`
|
|
28
|
+
- `parse5`
|
|
29
|
+
- `vite`
|
|
30
|
+
|
|
31
|
+
Astro, the required peer dependency, is also MIT-licensed. Playwright, used only
|
|
32
|
+
for development tests, is Apache-2.0 licensed.
|
|
33
|
+
|
|
34
|
+
## Transitive audit
|
|
35
|
+
|
|
36
|
+
The release audit found these licence families among installed production and
|
|
37
|
+
peer dependencies:
|
|
38
|
+
|
|
39
|
+
- MIT, ISC, BSD-2-Clause, BSD-3-Clause
|
|
40
|
+
- Apache-2.0, BlueOak-1.0.0
|
|
41
|
+
- CC0-1.0
|
|
42
|
+
- Python-2.0 (`argparse`)
|
|
43
|
+
- LGPL-3.0-or-later (the optional platform-specific `libvips` shared library
|
|
44
|
+
distributed for Sharp)
|
|
45
|
+
|
|
46
|
+
These licences permit commercial use, subject to their notice, attribution,
|
|
47
|
+
patent, source/relinking, and modification obligations. In particular, do not
|
|
48
|
+
statically incorporate or distribute a modified `libvips` build without reviewing
|
|
49
|
+
the LGPL requirements. Sharp's published platform packages use precompiled shared
|
|
50
|
+
libraries and carry their licence information in the installed package.
|
|
51
|
+
|
|
52
|
+
Full licence texts and copyright notices remain in each installed npm package.
|
|
53
|
+
JavaScript legal comments are preserved by esbuild when bundling by default.
|
|
54
|
+
|
|
55
|
+
Run `npm run check:licenses` and `npm sbom --omit=dev --sbom-format cyclonedx`
|
|
56
|
+
for every release. This inventory is a technical compliance aid, not legal advice.
|