Skip to content

Console static plugin: let the host add the Cloudflare Web Analytics beacon to the console document (a createConsoleStaticPlugin option); today nothing adds a head script or widens script-src #30936

Closed

Description

@objectstack-fleet

repo:cloud#1 · os-baozhoutao · session_013w7BrxKc6dUYe6Rw6tQW4G

Filing gate: ④ coordination, the platform half of objectstack-ai/cloud#3590, which carries ③ a task on the maintainer's word: 「同意立卡」 (2026-11-05, console traffic analytics for cloud.objectos.ai). That card's own instruction, verbatim: "Adding the script and the script-src entry may need a framework option, because the console document and its nonce CSP are the framework's. If so, file the platform card first and wait for it. ⛔ Do not rewrite the framework's CSP from cloud by string replacement."

Business reason. Nobody can say today how many people open the hosted console, or from where. cloud#3590 adds Cloudflare Web Analytics, which is free, sets no cookies and adds no new subprocessor. Every console document is served by this framework's console static plugin, so cloud cannot add the beacon without an option here.

Reader who acts. The lane that owns packages/cli (triage sets the lane). Then the cloud seat moves the pin and passes the option (cloud#3590 is pm:blocked on this card).

Dedupe (objectstack issues, open and closed): cloudflareinsights 0. createConsoleStaticPlugin script 12 and console CSP script-src 11; none asks for a host-supplied head script. The nearest is #28100 (Turnstile, a fixed origin read from env).

Measured (pin 2ffdb649 and objectstack main 26c965cb, the same at both)

All of this is in packages/cli/src/utils/console.ts:

  • createConsoleStaticPlugin(distPath, options?: { isDev?: boolean; rootRedirect?: boolean }) is the only console server.
  • readIndexHtml writes the base tag and the react-pages capability script, then stampScriptNonce nonces every script start tag.
  • serveDocument mints the nonce per request and calls consoleContentSecurityPolicy.
  • That policy's script-src is 'self' + nonce + 'wasm-unsafe-eval'. It adds 'unsafe-eval' only for react-pages, and TURNSTILE_ORIGIN only when both Turnstile env keys are set.
  • The only operator widening is OS_CONSOLE_CSP_CONNECT_SRC, which affects connect-src only. No option or env var adds a head script.

cloud reaches the plugin only through mountConsoleStatic (cloud packages/objectos-runtime/src/node-server.ts). The control plane and the tenant plane call it separately.

Ask

Add an option to createConsoleStaticPlugin, for example cloudflareWebAnalytics?: { token: string }.

  • Without it, the document and the policy stay byte-identical.
  • With it:
    1. Refuse at boot a token outside a narrow charset (Cloudflare site tokens are 32 hex characters), the way readExtraConnectSources refuses.
    2. In readIndexHtml, before stampScriptNonce, write the manual snippet so it carries the nonce: a deferred script tag whose src is https://static.cloudflareinsights.com/beacon.min.js, with a data-cf-beacon attribute holding the token JSON.
    3. Add https://cloudflareinsights.com to connect-src, documented in the policy jsdoc the way TURNSTILE_ORIGIN is.

It should be an option rather than an env var, so a host can pass it to one plane only. cloud passes it on the control plane and never on tenant planes (customers' apps, out of scope per cloud#3590), and that holds by construction.

Open point for the implementing lane, not decided here. cloud#3590's steps (written by the coordinator, not quoted from the maintainer) list https://static.cloudflareinsights.com in script-src too. A script element that carries the nonce loads whatever its host, so that entry is not needed to load the beacon. The jsdoc in this file warns that a CDN host in script-src admits every file the host serves. Measure whether the beacon loads anything further from that host before adding the entry.

Done when

  • Unit tests: with the option, the document carries the nonced beacon tag and the policy carries the new origin(s). Without it, both are byte-identical to today. A malformed token refuses boot.
  • cloud#3590 can pass the option at the control-plane mount once its pin carries this.

Activity

  1. added
    domain:serverDomain lane — seat card indexed by label:pm:seat
    enhancementNew feature or request
    pm:queueReady for the PM dispatch loop
    on Nov 6, 2026
  2. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: audit · p2 · domain:server · 2026-11-06T12:57Z

    ⛔ Not a claim.

    • The platform half of objectstack-ai/cloud#3590, which the maintainer approved (「同意立卡」). It asks for a createConsoleStaticPlugin option that adds the Cloudflare Web Analytics beacon to the console document, carrying the nonce, and adds https://cloudflareinsights.com to connect-src. Without the option, the document and the policy stay byte-identical.
    • Lane domain:server: packages/cli/src/utils/console.ts (the retired domain:cli reads as domain:server). Triage re-read the file on main: createConsoleStaticPlugin(distPath, options?: { isDev?; rootRedirect? }) at L999, and the policy at L853. No option or env var adds a head script today.
    • p2: a framework option (not a spec key), with no release need. It is cloud-blocking, because cloud#3590 is pm:blocked on it. pm:queue.
    • ⚠️ The card's open point stays with the implementing lane: whether https://static.cloudflareinsights.com goes into script-src. The nonced tag loads without that entry. If the lane finds it is needed, adding a CDN host to script-src widens a security boundary, so put it on a decision card first rather than in the PR.
    • Dedupe: no open card asks for a host-supplied head script (cloudflare analytics returns only this card). The nearest is plugin-auth: opt-in Cloudflare Turnstile on email sign-up (server verify + Console widget), off unless a site key and secret are configured #28100 (Turnstile, a fixed origin).

    Generated by Claude Code

  3. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7

    Claim: PM loop round 6 · 2026-11-06T13:21Z
    Session: session_016WV4cyvF7SP65frVFAYLv7
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: the card thread's designated claude/project-thread-* branch; this claim is re-posted whole with it once the thread reports it
    Worktree: objectstack-issue-30936
    Domain: domain:server
    Seat: domain:server#5 (this seat's share of the queue in created order)
    File surface: packages/cli/src/utils/console.ts (createConsoleStaticPlugin options, readIndexHtml, consoleContentSecurityPolicy connect-src and its jsdoc) and its tests in packages/cli/; the console static plugin's docs page if one documents the options; one .changeset/*.md with '@objectstack/cli': patch (or minor if the repo's rule for an added option says so). ⛔ script-src widening (triage 6685354963: a decision card first if the beacon needs it), ⛔ env vars, ⛔ cloud
    Container & model: M, mode:thread, model: default, effort: high
    Clause-②: no (an opt-in option; without it the document and the policy stay byte-identical; the dev re-derives it from the diff)
    Metadata-protocol: none
    Responsibility: no host can add a head script to the console document or widen its policy for one, so the hosted console has no traffic analytics (cloud#3590, pm:blocked on this card) | none | the cloud control-plane mount (mountConsoleStatic)
    Thread-read: 6685354963
    Premise: holds on main 33043bd8: createConsoleStaticPlugin(distPath, options?: { isDev?; rootRedirect? }) at L999, consoleContentSecurityPolicy at L853, TURNSTILE_ORIGIN at L887; no option or env var adds a head script
    Serial constraints cleared: no open PR touches packages/cli/src/utils/console.ts (read 13:22Z). The dev re-checks before the first push

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 6, 2026
  5. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7

    Claim: PM loop round 6 · 2026-11-06T13:23Z (re-posted whole with the branch, superseding the claim above)
    Session: session_016WV4cyvF7SP65frVFAYLv7
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/project-thread-5iqzi1 (the card thread's designated branch, dev session session_016WV4cxGr3fDvcDkCLUwZAo), branched from main 33043bd
    Worktree: objectstack-issue-30936
    Domain: domain:server
    Seat: domain:server#5 (this seat's share of the queue in created order)
    File surface: packages/cli/src/utils/console.ts (createConsoleStaticPlugin options, readIndexHtml, consoleContentSecurityPolicy connect-src and its jsdoc) and its tests in packages/cli/; the console static plugin's docs page if one documents the options; one .changeset/*.md with '@objectstack/cli': patch (or minor if the repo's rule for an added option says so). ⛔ script-src widening (triage 6685354963: a decision card first if the beacon needs it), ⛔ env vars, ⛔ cloud
    Container & model: M, mode:thread, model: default, effort: high
    Clause-②: no (an opt-in option; without it the document and the policy stay byte-identical; the dev re-derives it from the diff)
    Metadata-protocol: none
    Responsibility: no host can add a head script to the console document or widen its policy for one, so the hosted console has no traffic analytics (cloud#3590, pm:blocked on this card) | none | the cloud control-plane mount (mountConsoleStatic)
    Thread-read: 6685354963
    Premise: holds on main 33043bd8: createConsoleStaticPlugin(distPath, options?: { isDev?; rootRedirect? }) at L999, consoleContentSecurityPolicy at L853, TURNSTILE_ORIGIN at L887; no option or env var adds a head script
    Serial constraints cleared: no open PR touches packages/cli/src/utils/console.ts (read 13:22Z). The dev re-checks before the first push

  6. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4cxGr3fDvcDkCLUwZAo
    os-dev-report

    {
     "issue": 30936,
     "status": "done",
     "branch": "claude/project-thread-5iqzi1",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/30961",
     "head": "cbef4f627b",
     "session": "session_016WV4cxGr3fDvcDkCLUwZAo",
     "premise_still_valid": true,
     "summary": "createConsoleStaticPlugin takes cloudflareWebAnalytics: { token }. With it, readIndexHtml writes a deferred beacon script tag (src https://static.cloudflareinsights.com/beacon.min.js, data-cf-beacon = escaped JSON of the token) before stampScriptNonce, so it carries the document nonce, and consoleContentSecurityPolicy adds https://cloudflareinsights.com to connect-src (documented in the policy jsdoc beside TURNSTILE_ORIGIN). A token that is not 32 hex digits refuses start() without echoing the value. script-src is unchanged. Without the option the document and the header are byte-identical to main f108e6d2 (pinned against literals captured from the unmodified module). The published-subpath pin carries the widened options type. One changeset, '@objectstack/cli': patch.",
     "tests": "pnpm --filter @objectstack/cli exec vitest run --project unit src/utils/console.content-security-policy.test.ts: Tests 66 passed (66). cli typecheck: lock VERDICT command-exit 0. After pnpm --filter @objectstack/cli build: published-subpath-console + hook-body pins Tests 29 passed (29). Whole unit project before the CLI build: 387/389 files, 5469 passed; the 2 failures were the two published-subpath pins refusing on a missing packages/cli/dist, green after the build. Integration layer declared to CI. Ablations via scripts/ablation-replace.mjs on console.ts (src-imported, no dist leg), each landed (anchor 1 -> 0, blob 96f2c47898c3 -> new) and restored (blob == HEAD, git diff HEAD empty): A1 no beacon tag -> 2 failed; A2 report origin unconditional -> 16 failed; A3 token pattern matches everything -> 7 failed; A4 attribute unescaped -> 2 failed. Pin control: old options type in the conformance fixture -> TS2344, 1 failed; restored.",
     "gates": "At cbef4f627b: dispatch-gates --commands --repo objectstack-ai/objectstack derived 62; all 62 plus pnpm lint, check:cli-command-ids, check:adr-symbol-anchors ran, 65 exit 0 (check:dual-build-cjs-loads and check:i18n-coverage first exit 3 PREREQUISITE NOT MET, exit 0 after turbo run build --concurrency=2). dispatch-gates --ran: exit 0, 62 run, 0 NOT-MEASURED. check-commit-card-trailers --range origin/main..HEAD: exit 0. PR check-runs: in_progress at report time. Record: /mnt/project-files/server-lane-seat5/30936/gates-ran-cbef4f62.txt. origin/main moved 5 commits after the branch point, none touching console.ts or the two test files; not merged in.",
     "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
     "mcp_calls": "3 — mcp__github__issue_read x2 (card body, comments), mcp__github__list_pull_requests x1; reads only, no write tool. Plus 1 gh api read of three PRs' file lists (serial check), no write.",
     "api_writes": "2 — fleet-write relay pr_create (objectstack#30961, read-back identical 13940/13940 bytes); post-stamped comment (this report). Plus 1 git push.",
     "open_questions": [
      {
       "question": "script-src for the beacon host (the card's open point): does beacon.min.js load anything further from static.cloudflareinsights.com? NOT MEASURED here: this container's egress proxy refuses that host (CONNECT 403). The nonced tag itself loads without a host entry.",
       "options": [
        "A Land as is (no script-src entry); measure in a browser on the cloud control plane once cloud#3590 passes the option, and file a card only if a CSP report names that host",
        "B Hold landing until someone with network reach reads beacon.min.js"
       ],
       "recommendation": "A, because the nonce admits the beacon element whatever its src, the policy stays at its tighter boundary, and widening script-src is the seat's decision card anyway (triage 6685354963)"
      },
      {
       "question": "Clause-②: the claim reads no; the diff widens the options bag of createConsoleStaticPlugin on the published @objectstack/cli/console subpath (pinned by shape), so the PR body and changeset declare yes (widening). Keep?",
       "options": [
        "A Keep yes (widening)",
        "B Re-post as no"
       ],
       "recommendation": "A, because the line asks whether the public surface grows, and precedent changesets declare an added optional config key as yes (widening)"
      }
     ],
     "security_findings": "none",
     "out_of_scope_findings": [
      "carrier: none · noted, not filed: src/console.ts header and the published-subpath pin docblock call a widening of that subpath a minor-bumped act; AGENTS.md Post-Task Checklist step 3 sets patch for Metadata-protocol none. Changeset follows AGENTS.md; the stale prose is left as is."
     ]
    }
  7. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7

    Landed · 2026-11-06T15:34Z · PR #30961

    Probe Reading
    PR #30961 merged, merge commit 40ecdf97, through the merge queue (seat review PASS 6687185923 and seat ACCEPT 6687200203, both on head cbef4f62; not governed, +236/-3, TIER_DEFAULT with Clause-②: yes (widening); every check green or expected-skip before landing)
    #30936 closed by Fixes #30936

    Shipped: the console static plugin in @objectstack/cli takes an optional cloudflareWebAnalytics: { token } option (token must be 32 hex). When set, the console document carries a nonced, deferred Cloudflare beacon tag and connect-src gains https://cloudflareinsights.com; without it nothing changes. The control-plane mount (cloud#3590) can now pass the option.

    Owed, not measured here: whether the beacon loads further scripts that script-src would refuse. The fleet's egress blocks static.cloudflareinsights.com, so this needs one browser reading on the control-plane console once the option is on.

    State change: pm:dispatched removed, assignee cleared.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions