feat: public note sharing (/s/<slug>) + management API - #3
Conversation
## What's new ### Public note serving - GET /s/<slug> renders Obsidian markdown notes as clean HTML - Responsive light theme with system fonts, good typography - Published date shown in header - Subtle footer: 'Shared via Giskard 🔮' - Clean 404 page for unknown slugs ### Management API (protected by X-Publish-Token header) - PUT /api/publish — publish or update a note - DELETE /api/publish/<slug> — unpublish a note - GET /api/published — list all published notes with metadata ### Markdown rendering - Uses 'marked' library (bundled via wrangler/esbuild) - Supports: headers, bold/italic, links, code blocks, tables, blockquotes, images, lists ### Infrastructure - KV namespace: PUBLISHED_NOTES (cf83f0a936324351985100e78363da07) - PUBLISH_TOKEN secret stored in Cloudflare + pass obsidian-redirect/publish-token - Existing Obsidian URI redirect routes preserved unchanged
## Infrastructure as Code All Cloudflare infra for obs.gisk0.dev is now managed via Terraform using the cloudflare/cloudflare provider (v5.17). ### Resources managed - cloudflare_workers_kv_namespace.published_notes — PUBLISHED_NOTES KV - cloudflare_workers_script.obsidian_redirect — bundled TS worker - cloudflare_workers_route.obs_gisk0_dev — obs.gisk0.dev/* route ### Workflow make tf-init # first time only make tf-plan # see changes make tf-apply # deploy make deploy # build + apply in one shot ### Secrets - CLOUDFLARE_API_TOKEN from pass cloudflare/workers-api-token - PUBLISH_TOKEN from pass obsidian-redirect/publish-token - Never hardcoded, read at plan/apply time via pass ### State State stored locally in terraform/terraform.tfstate (gitignored). Provider lock file committed for reproducible builds. ### Tool Uses OpenTofu (open-source Terraform fork, full HCL/provider compat). Install: https://opentofu.org
♻️ Updated: Terraform IaC addedAll Cloudflare infrastructure is now managed via Terraform (OpenTofu) in Resources under Terraform control:
Workflow: make tf-init # first time
make tf-plan # preview changes
make tf-apply # deploy (build → apply)Secrets read from
|
PR Review: #3 — feat: public note sharing (/s/) + management APIVerdict: Approve with suggestions Summary: Clean, well-structured extension of the existing worker. The code is readable, the Terraform setup is solid, secrets are handled properly via 🔴 Blockers (must fix before merge)
🟡 Warnings (should fix, not blocking)
💡 Suggestions (optional but worth considering)
✅ Good Stuff
Test Coverage: Manual integration tests documented in PR — solid for this scope. No unit tests, but the worker is thin enough that integration tests cover the surface. |
Blockers: - Wrap req.json() in try/catch, return 400 on malformed JSON - Add HTML sanitizer (strips script/iframe/object/embed tags, event handlers, javascript:/vbscript:/data: URLs) — CF Workers compatible Warnings: - Validate slugs: only [a-z0-9-], max 128 chars, reject path traversal - Reject request bodies > 1MB via Content-Length check - Replace N+1 KV reads in list endpoint with _meta:index key (single KV read, updated on publish/delete) - Move hardcoded account_id/zone_id to terraform.tfvars (gitignored) - KV namespace ID already consistent between wrangler.toml and TF state - TF provider v5.17 schema verified compatible Suggestions: - Add Cache-Control: public, max-age=300 on public note responses - Add defensive 404 for unmatched /api/ and /s/ paths - Drop @types/marked (marked v17 has built-in types) Ref: PR #3 review comment
- CI on PRs: TruffleHog secret scan, typecheck + build, tofu validate + fmt - Secret scan on all pushes via TruffleHog
- Add Trunk with TruffleHog, Prettier, ESLint, shellcheck, tflint, checkov - Replace standalone secret-scan.yml with trunk-io/trunk-action - Unified CI workflow: Quality Checks (trunk + build + typecheck + terraform) - Add package-lock.json (fixes npm ci cache error) - Terraform validate now runs after build (fixes missing dist/index.js) - Add required_version to terraform block (fixes tflint) - Add permissions: contents: read to deploy.yml (fixes checkov) - Auto-format all files with trunk fmt (prettier, shfmt, taplo)
PR Review: #3 — feat: public note sharing (/s/) + management APIVerdict: Approve ✅ Summary: Clean, well-structured extension of the Cloudflare Worker adding public note sharing with token-authed management API, Terraform IaC, and Trunk CI. Code quality is high — good input validation, sanitization, defensive routing, and the meta-index pattern avoids N+1 KV reads. No blockers. 🔴 Blockers (must fix before merge)None. 🟡 Warnings (should fix, not blocking)
💡 Suggestions (optional but worth considering)
✅ Good Stuff
Test Coverage: No automated tests in repo (manual curl verification noted in PR). Acceptable for a personal tool of this size. |
Security (warnings): 1. Replace regex-based HTML sanitizer with allowlist-based approach — only permitted tags/attributes survive, everything else is stripped 2. Add post-parse body size checks (markdown 512KB, title 1KB) — Content-Length header alone is spoofable 3. Use crypto.subtle.timingSafeEqual() for token comparison — prevents timing side-channel attacks 4. Replace real CF account/zone IDs with placeholders in terraform/README.md Improvements (suggestions): 5. Add single-quote escaping (') to escHtml() 6. Add comments in wrangler.toml documenting KV namespace ID must match Terraform state, with command to retrieve it 7. Eliminate redundant npm ci + build in CI terraform job — use upload/download-artifact from the build job instead
Summary
Extends the
obs.gisk0.devCloudflare Worker with a public note sharing feature, so Obsidian notes can be shared as clean HTML pages.New Routes
/s/<slug>/api/publish/api/publish/<slug>/api/publishedAuth is via
X-Publish-Tokenheader. Token stored inpass obsidian-redirect/publish-token.Infrastructure
PUBLISHED_NOTES(id:cf83f0a936324351985100e78363da07){ title, markdown, publishedAt, updatedAt }marked(bundled, ~62 KiB gzip 16 KiB)PUBLISH_TOKENset via wrangler secretHTML Template
Helper Script
scripts/publish-note.shin the Giskard vault lets you publish any vault note:Testing ✅
All tests pass on the deployed worker: