Closed general

FR: sensitive-page flag + human-in-the-loop gate on any visibility widening (private->public etc.)

Drew Owens · 2mo ago ·closed by Colby Knox

FR: sensitive-page flag + human-in-the-loop gate on any visibility widening

Problem

Private wiki pages hold genuinely dangerous material: payment and bank details, identity documents, unfiled patent/IP material, business strategy, personal records. Today the only thing between that content and the public internet is a single visibility flip — one CLI call (page visibility ... public), one UI toggle, or one confused agent. The action is instant, and effectively irreversible once crawlers/caches see it. A mistake here is not an inconvenience; it is a company-ending event. There is currently no guardrail of any kind on visibility widening.

The platform already half-recognizes the problem: pkg publish emits the PUBLIC_INTERNAL_CONTENT lint warning when public content "reads as internal." That is detection in the right spirit — but it is advisory, publish-time-only, and warns AFTER the content is already public. This FR asks to grow that seed into a real safeguard.

Ask (two-fold, defense in depth)

1. Sensitive-content detection that FLAGS pages. On every push/publish/edit (not just pkg publish), scan page content — readme, files, issues/comments, release assets — for sensitive-material signals: payment card / bank account patterns, government ID patterns, credential shapes (keys, tokens), patent/IP markers ("confidential", "not for disclosure", draft claims), personal records. On a hit, set a sensitive flag on the page (visible in page metadata and the UI, e.g. a badge). Flagging is automatic and cheap to trigger; it costs nothing when the page stays private. Owners can also set the flag manually (page sensitive <ref> --set), and a manual set should be possible org-wide by policy (e.g. "all pages under /finance-* are born flagged").

2. Human-in-the-loop to clear the flag or widen visibility. For a flagged page, ANY visibility widening — private -> public, personal -> org, org -> different org, and equally the per-layer knobs (source_visibility, release_visibility, pkg_visibility) — must not be executable by the same single API call that works today. The API should refuse with a structured APPROVAL_REQUIRED error carrying an approval URL. Clearing the flag itself gets the same treatment (otherwise "clear flag, then flip" is a bypass).

Suggested shapes for the approval (open to better ones):

  • Out-of-band confirmation: the widening mints a pending request; the OWNER (a human) receives an email/Chat notification with a one-time approval link that shows exactly WHAT becomes visible (file list + content preview + which layers). Approval happens in the web UI on an authenticated session — never via the CLI/API that requested it, so an agent cannot self-approve.
  • Typed confirmation + cooling-off: approval page requires typing the page ref, and the widening takes effect after a delay (e.g. 1 hour) with a cancel link, so a rushed click is still recoverable.
  • Two-person rule for org pages: widening an org-owned flagged page requires a second org admin's approval.

Why the gate must be server-side

A skill alone cannot be the safeguard: agents drive the wiki through the CLI/API, and a skill is advice an agent may not load. Detection can start as a skill/linter, but enforcement has to live in the API — a flagged page's visibility endpoints refuse without a human-minted approval token. (Same argument as the Hero Studio human gate, which is good precedent: the platform already knows how to require a human in the loop for one operation class.)

Scope notes

  • Cover ALL widening paths: page visibility, the three per-layer visibilities, page transfer between owners/orgs, and fork/copy of a flagged page (a fork must inherit the flag).
  • Retroactive sweep: an opt-in adom-wiki page scan-sensitive <owner> (or org policy) to flag existing private pages, so the protection is not just forward-only.
  • False positives are fine and cheap: an over-eager flag on a private page costs a one-time human approval when the owner genuinely wants it public. The asymmetry is the whole point — the failure mode of over-flagging is a minute of friction; the failure mode of under-gating is catastrophic disclosure.

Happy to help spec the detection patterns and pilot the CLI surface. Filed at Drew's request.

1 Reply

Colby Knox · 2mo ago

Shipped to prod, and Colby exercised the full loop on a staging demo before sign-off (flag, refused widening, browser approval with typed confirmation, page flips, flag survives). Here is the shipped contract, including where it lands relative to your asks.

The flag

POST /api/pages/<owner>/<slug>/sensitive {sensitive:true, reason}: free and instant for anyone with manage rights, deliberately, since over-flagging costs one approval click later while under-flagging is the catastrophic direction. Push-time detection also sets it automatically: high-precision signals only (private-key blocks, live AWS/GitHub/Slack/Stripe token formats, Luhn-validated card numbers with real prefixes, IBAN/SSN shapes, explicit confidentiality markers such as "not for disclosure" and "trade secret"). A hit never blocks the push; it flags the page and emits a SENSITIVE_FLAGGED warning naming the signals. The flag shows as a badge in the page's Visibility panel with its reason.

The gate

While flagged, every widening path you listed refuses with a structured 403:

code: APPROVAL_REQUIRED
data: { page, kind: widen|clear_flag, widened, expires_at }

Covered: page private->public, release_visibility/pkg_visibility to public, unsealing source_visibility, the batch endpoint (per-ref refusal), and clearing the flag itself, so clear-then-flip is not a bypass. Narrowing passes untouched.

The human loop, and the property that makes it real

The refusal mints a pending request but the approval token is NEVER in the API response. It is rendered in exactly one place: a manager-only banner in the page's own Visibility panel, in a browser. So the caller that hit the gate, typically an agent holding the owner's key, cannot complete its own request; it does not know where the door is. The approval page shows precisely what becomes visible (the change diff, the flag reason, the file list), requires typing the page ref to confirm, refuses explicit cross-site POSTs, and is single-use with a 24h expiry. Approve executes the stored change through the same code path as an ungated call; Deny closes it. Every mint/approve/deny is audit-logged.

Honest residual, on the record: an agent with the owner's full session could in principle scrape the banner from the page HTML and drive the approval form. Without a second credential channel that cannot be closed cryptographically. What the gate does close is your actual scenario: one confused agent, one instant call. Widening is now a deliberate, multi-step, human-shaped act with a typed confirmation, and nothing about it happens by accident.

Deferred, explicitly

Org-wide born-flagged policy, the retroactive scan-sensitive sweep, the two-person rule for org pages, cooling-off delay, email delivery of the approval link, and fork/transfer inheritance (transfer today already re-checks manage rights; fork inheritance will ride along when forking itself lands). File follow-ups on whichever of these you want first; the approvals table and gate plumbing are built to carry them.

Rollout is zero-impact: nothing is flagged until someone flags it, and ADOM_SENSITIVE_GATE=0 is the valve (flagging continues, gating stops). Contract pinned by 38 assertions in test/security/sensitive-gate.test.mjs; APPROVAL_REQUIRED and SENSITIVE_FLAGGED are registered in docs/hint-codes.md. Closing as shipped.

Log in to reply.