shlook
v0.2.4
Published
Private-by-default artifact hosting for agents
Readme
shlook
shlook is a reusable, self-hosted Cloudflare package for publishing agent-created HTML,
static sites, and raster images. Publications are private by default; public and secret-link
sharing require an explicit visibility change.
Security model
A deployment has four browser origins with different trust levels:
| Origin | Purpose | Cloudflare Access | | ------- | ------------------------------------------ | ----------------- | | Owner | Owner UI/API and isolated archive previews | Required | | Private | Owner-authenticated artifact viewing | Required | | Public | Stable public artifact URLs | Not enabled | | Share | Secret capability-link artifacts | Not enabled |
All four must be distinct, pathless HTTPS origins.
Untrusted artifacts can contain active HTML and JavaScript. Normal artifact views must never
share an origin with the owner UI or mutation API: same-origin artifact code could send
owner-authorized API requests. The archive's authenticated /preview/assets/** route is a
narrow exception whose iframe and response policies force an opaque sandbox, deny network and
form actions, and block cross-site or passive browser-resource requests to /api/**. For that
reason, general one-origin path multiplexing such as /api, /private, and /share on one
hostname is not supported. Paths alone are not a browser security boundary.
Deployment modes
Choose one of these secure topologies:
- Account with a domain: deploy one Worker with four custom hostnames, for example
owner.example.com,private.example.com,public.example.com, andshare.example.com. Wrangler routes withcustom_domain: truemanage custom-domain DNS and certificates during deployment. Disable theworkers.devand preview URLs so they cannot bypass the hostname policies. - Account without a domain: deploy four distinct Worker names. Their production URLs
become four origins such as
shlook-owner.<workers-subdomain>.workers.dev,shlook-private.<workers-subdomain>.workers.dev,shlook-public.<workers-subdomain>.workers.dev, andshlook-share.<workers-subdomain>.workers.dev. Enable Access separately on the owner and privateworkers.devroutes; leave public and share unauthenticated.
All four surfaces use the same D1 database and private R2 bucket. D1 stores metadata and
lifecycle state; R2 stores artifact bytes and must not have an r2.dev URL or public custom
domain. Apply the packaged D1 migrations before serving traffic.
Every deployed Worker must receive these non-secret variables through its operator-owned Wrangler configuration:
SHLOOK_OWNER_ORIGIN=https://<owner-host>
SHLOOK_PRIVATE_ORIGIN=https://<private-host>
SHLOOK_PUBLIC_ORIGIN=https://<public-host>
SHLOOK_SHARE_ORIGIN=https://<share-host>
SHLOOK_OWNER_EMAIL=<owner-email>The owner Worker also requires a 32-byte base64 encryption key stored as a Cloudflare Worker
secret named SHLOOK_SECRET_ENCRYPTION_KEY. It encrypts recoverable capability URLs at rest;
never place it in vars, source control, or agent output.
Set these operator-owned values for every CLI or agent environment:
export SHLOOK_API_ORIGIN="https://<owner-host>"
export SHLOOK_PRIVATE_ORIGIN="https://<private-host>"
export SHLOOK_PUBLIC_ORIGIN="https://<public-host>"
export SHLOOK_SHARE_ORIGIN="https://<share-host>"
export CF_ACCESS_CLIENT_ID="<agent-service-token-client-id>"
export CF_ACCESS_CLIENT_SECRET="<agent-service-token-client-secret>"Do not rely on package defaults for a self-hosted installation. See
skills/shlook/references/setup.md for the manual
operator checklist. The packaged shlook setup command does not provision Cloudflare resources,
DNS, Worker routes, Access applications, policies, or service tokens.
Publish an artifact
Every new publication has a concise display name and may have a description:
shlook publish ./artifact \
--name "Owner archive refinement" \
--description "Responsive archive controls and theme study" \
--jsonNames are required by the current CLI and accept 1-80 characters. Descriptions are optional and accept up to 500 characters. Both are stored in D1, returned by the owner API, and shown and searched in the owner archive. Existing assets are assigned their prior eight-character ID prefix when the metadata migration is applied.
The creation API accepts the same metadata as JSON:
{
"name": "Owner archive refinement",
"description": "Responsive archive controls and theme study"
}Route grammar
- Owner:
/,/archive,/health, and/api/assets... - Private:
/latest[/<artifact-path>]and/assets/<asset-id>[/<artifact-path>] - Public:
/assets/<asset-id>[/<artifact-path>] - Share:
/s/<capability>/assets/<asset-id>[/<artifact-path>]
Unknown routes and artifacts unavailable to the requested audience return 404.
Development
Requirements: Node.js 24 and pnpm 11.17.0.
pnpm install --frozen-lockfile
pnpm fmt
pnpm run ciOxlint owns linting and Oxfmt owns formatting. pnpm run ci runs lint, format checking,
strict TypeScript checking, tests, and a Wrangler dry-run build.
License
MIT. See LICENSE.
