markdown/deck/html_fragment rendered author-influenced content verbatim,
so <script>/<img onerror>/etc. executed on the Daybook origin — self-XSS
against the owner's session, and (post-sharing) unauthenticated via /s/.
Two layers, both in artifacts/views.py:
- Create-time allowlist sanitization via nh3 (maintained ammonia/Rust
sanitizer, not deprecated bleach). Keeps formatting/tables/links/images
(incl. relative /assets URLs) and codehilite markup; drops script, on*
handlers, style, unknown tags, and javascript:/data: URLs. html_full is
deliberately NOT sanitized (documented verbatim escape hatch).
- Serve-time CSP (script-src 'none') for the sanitized kinds, on both
/a/{id} and the unauthenticated /s/{token} route. Being a response
header, it also neutralizes scripts in artifacts stored before this fix.
Not applied to html_full or json/csv (json/csv data is already escaped;
their viewers need their own JS).
Verified live across all kinds; 7 regression tests added. 102 tests pass.
Not addressed (noted in the issue): CSV formula injection,
CSRF_COOKIE_HTTPONLY (moot with XSS closed), nonce CSP for json/csv/full.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9.1 KiB
| name | description |
|---|---|
| daybook-posting | Use when you have produced a finished deliverable such as a report, summary, brief, diagram, generated HTML, PDF, deck, or status update that a human should review later outside this chat. Publishes the artifact to Daybook and returns a URL. |
Daybook Posting
Daybook is a private artifact journal for completed agent outputs. It stores, indexes, and displays artifacts for human review. It does not execute commands, run workflows, or continue conversations.
Always use the helper script. Do not hand-build curl, JSON, PowerShell multipart, or inline shell content.
From the project root:
python skills/daybook-posting/scripts/daybook_post.py --check
python skills/daybook-posting/scripts/daybook_post.py --kind markdown --title "Build Report" --content-file report.md
cat report.md | python skills/daybook-posting/scripts/daybook_post.py --kind markdown --title "Build Report"
python skills/daybook-posting/scripts/daybook_post.py --file diagram.svg --title "Architecture Diagram"
The project also keeps a compatibility wrapper at scripts/daybook_post.py, but the canonical helper is bundled inside this skill.
The artifact URL is printed as the final stdout line.
Configuration
Set these as real process/shell environment variables (export on
Linux/macOS, $env: or set on Windows, or your agent harness's own env
config) — the helper reads os.environ directly. There is no .env file
support; a .env file in the working directory is not read automatically.
DAYBOOK_URL=http://127.0.0.1:8001
DAYBOOK_TOKEN=your-personal-api-token
DAYBOOK_SOURCE=agent
DAYBOOK_TOKEN is your personal per-user API token, from Settings → Account in the app (not a token shared by everyone). Artifacts posted with your token land only in your feed.
Kind Cheat Sheet
| Kind | Use for | Content shape |
|---|---|---|
markdown |
Reports, briefs, notes | Markdown text. Fenced code blocks get syntax highlighting; tables (pipe syntax) render styled automatically. |
html_fragment |
Themed HTML reports/cards/tables | Body-only HTML. No html/head/body. Tables render styled automatically, same as markdown. |
html_full |
Complete custom pages/frameworks | Full HTML document containing <html> (<!doctype> recommended, not required). See "CDN Snippets" below. |
deck |
Simple slide-like artifact | Top-level <section>...</section> blocks. |
json |
Structured data, metrics, run summaries | Valid JSON text. Renders as a collapsible tree. |
csv |
Tabular data | CSV text, first row as header. Renders as a sortable/searchable table. .xlsx files convert to this automatically — see below. |
For images, PDFs, SVGs, and other files, use --file; the helper infers multipart and content type.
HTML in markdown, html_fragment, and deck is sanitized — ordinary
formatting, tables, links, and images are kept, but <script>, event
handlers, <style>, and javascript:/data: URLs are stripped. If you
need JavaScript (a chart library, an interactive widget), use html_full,
which is served verbatim — that's what it's for. Don't put scripts in the
themed kinds expecting them to run; they won't.
--theme (for markdown/html_fragment only) accepts report (default —
serif, warm background), tasks, or base (minimal, no color styling).
deck/html_full/json/csv ignore --theme — each has its own fixed
presentation.
This script only creates artifacts — none of the following are
possible through it. Updating an existing artifact's title/tags/metadata,
or deleting one, requires the raw API (see docs/api.md); enabling its
public share link is feed-UI-only, with no API equivalent at all yet.
CDN Snippets For html_full
html_full is Daybook's escape hatch for custom pages — nothing is
injected into it, so a poster wanting Tailwind or a charting library has to
supply its own <link>/<script> tags. Known-good ones:
<!-- Tailwind (Play CDN - compiles in-browser on every load; fine for a
one-off artifact, not something you'd want on a high-traffic page) -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
Offline caveat: Daybook can run fully self-hosted/air-gapped. Any
artifact that links a CDN script won't render fully (or at all, for
Tailwind's in-browser compiler) without internet access. If the deployment
target is offline, don't reach for these — use markdown/html_fragment
instead, which are styled entirely server-side with no external requests.
Self-hosted alternative for tables/JSON — no CDN needed: Daybook already
vendors Grid.js and renderjson locally (used to render csv/json
artifacts), and they're reachable as plain static files from any artifact,
including html_full:
<link rel="stylesheet" href="/static/vendor/gridjs/mermaid.min.css">
<script src="/static/vendor/gridjs/gridjs.umd.js"></script>
<!-- or, for a collapsible JSON tree: -->
<script src="/static/vendor/renderjson/renderjson.js"></script>
Prefer these over an external CDN for table/JSON rendering inside a custom
html_full page — same libraries, but self-hosted, so they work offline
and don't depend on a third party staying up. This only covers
tables/JSON; there's no vendored equivalent for Tailwind/Chart.js, so the
CDN snippets above are still the only option for those.
Only use this for html_full specifically — never add CDN references to
markdown/html_fragment/deck. Those kinds exist so Daybook, not the
poster, owns styling; injecting a default CDN habit into them would
undermine that.
Images Inside Content
To embed an image within a markdown or HTML artifact (rather than posting it as its own artifact), upload it first and reference the returned URL:
# 1. Upload -> prints the URL as the last line
python skills/daybook-posting/scripts/daybook_post.py --upload-image chart.png
# 2. Reference that URL in your markdown, then post as usual
# 
python skills/daybook-posting/scripts/daybook_post.py report.md
The image is linked to the artifact that references it and is deleted along with it. Unreferenced uploads are cleaned up after 24 hours.
CSV, JSON, And Spreadsheets
For CSV and JSON, use --kind csv/--kind json with --content-file — or
just post the file positionally; auto-mode detects .csv/.json and picks
the right kind automatically:
python skills/daybook-posting/scripts/daybook_post.py results.csv
python skills/daybook-posting/scripts/daybook_post.py --kind json --title "Run Summary" --content-file summary.json
.xlsx spreadsheets work too, with no special flag needed — just --file
(or post it positionally, same as any other file):
python skills/daybook-posting/scripts/daybook_post.py balance_sheet.xlsx
The server converts it to a csv artifact automatically. Known
limitations (see docs/api.md for the full list):
- Only the workbook's active sheet converts; other sheets are dropped.
- Formula cells need to have been calculated and saved by real spreadsheet software (Excel, LibreOffice, etc.) — a workbook a script wrote but never opened in a real spreadsheet app will show those cells blank.
- Rich formatting (bold, colors, merged cells) isn't preserved — it's a plain sortable/searchable table either way.
- Legacy
.xlsisn't supported, only.xlsx.
Other Code
For code that isn't CSV/JSON but should render today rather than download
as a binary, use --file path --as-markdown to wrap it in a Markdown
fenced code block (still gets syntax highlighting):
python skills/daybook-posting/scripts/daybook_post.py --file script.py --as-markdown --title "Generated Script"
Without --as-markdown, it uploads as a downloadable binary file instead.
Source & Provenance Fields
All optional, all default to something reasonable if omitted:
| Flag | Default | Notes |
|---|---|---|
--source-type |
agent |
One of agent, person, system. |
--source-name |
Same as the effective source (--source, or DAYBOOK_SOURCE if --source wasn't given) |
Human-readable name, e.g. "Hermes Agent". |
--source-id |
Empty string | A stable external ID, if the poster has one. |
--source-prompt |
Empty string | The prompt/instruction that produced this artifact — useful for later tracing why something was posted. |
Use --meta key=value (repeatable) or --meta-json '{"key": "value"}'
(merged together if both are given) for structured, machine-readable
context like run_id or model — these end up in the artifact's
metadata field, distinct from tags.
Rules
- Run
--checkfirst if unsure Daybook is reachable. - Use a stable
DAYBOOK_SOURCEor pass--source(the agent-profile, script, or person name that produced this artifact). - Use
--tags a,b,cfor filtering. - Use
--meta run_id=... --meta model=...for machine-readable context. - Never inline multiline content into the shell command; use a file or stdin.
- Add
--dry-runto see exactly what would be sent (method, headers, payload) without actually posting — useful when unsure a payload is well-formed.