Skip to content

Draft stable candidate: optional password and Slack code sign-in - #208

Draft
makeitfutureDev wants to merge 52 commits into
mainfrom
fix/password-slack-login
Draft

makeitfutureDev wants to merge 52 commits into
mainfrom
fix/password-slack-login

Conversation

@makeitfutureDev

Copy link
Copy Markdown
Owner

Draft stable promotion candidate requested for optional password and Slack-code dashboard sign-in.

Exact candidate: f2e530d (fix/password-slack-login). This branch includes the current beta history; main does not yet contain the passkey implementation this change extends. This is NOT a standalone hotfix and must not be merged as one.

Scope beyond the new sign-in checkbox: individual user-linked admin passkeys and recovery, existing admin/security hardening, SSH author runtime selection, Slack code channels and status alias, linked working-folder behavior, running effort/activity labels, harness defaults, shared admin controls and Composio Workbench fixes. Review the complete commit/files comparison before deciding stable scope. No version tag or release artifact is created by this PR.

Evidence for this exact candidate:

  • CI passes on Node 22.13 and Node 24; documentation build, hygiene and DCO pass.
  • Local full coverage: 4,120 passed, 38 opt-in skipped, zero failed; coverage 93.91% lines, 86.55% branches, 90.27% functions.
  • Real Chromium fixtures: 15 combined passkey/bootstrap/alternate tests passed, and the extended alternative-login disable/sign-out flow passed separately. Private message delivery is simulated.
  • Dedicated OTP/race tests, independent review, security coverage, static check and secret scan pass.
  • Root production audit passes at high threshold (three existing moderate advisories). Documentation dependency audit now reports zero vulnerabilities after three compatible transitive patches.

Release blockers:

  • No human live Slack-code/RustDesk acceptance has been executed. Relevant engine-independent procedure is in TEST-PLAN.md. Other applicable beta release acceptance gates have not been rerun for this promotion; 38 opt-in skips are not passes.
  • The private QA registry update is missing because the requesting user's personal Airtable connection is unavailable. Do not substitute another identity.
  • Organization administrator confirmation of this exact tested candidate and any explicit live-check deferrals is still required by AGENTS.md and AGENTS.local.md. This draft is not release authorization.
  • Development PR Allow optional password and Slack code admin sign-in #207 targets beta; the served checkout remains on beta and has unrelated pre-existing working changes. Do not overwrite those changes to land this promotion.

tbiyss and others added 30 commits October 23, 2026 12:05
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Security audit follow-up, first batch (no functionality removed for anyone
acting within policy):

- Slack Settings mode control: only an organization admin can switch a
  channel INTO Admin mode (the rule /mode admin and set_channel_admin_mode
  already enforce). A channel manager keeps every other control in an
  Admin-mode channel and may step it down; the picker lists "Admin" only to
  an admin or when it is the current mode.
- Case-variant URLs: Express case-sensitive routing plus lowercase-aware
  path guards, so /API/settings is refused like /api/settings instead of
  slipping past the Host/Origin guard, the lockdown and the 401 branch.
- Security headers: nosniff, X-Frame-Options DENY and Referrer-Policy on
  every response; a no-inline-script Content-Security-Policy on the admin
  shell (import map admitted by content hash) and the login page, whose
  handler moves to public/login.js.
- One Express error handler (bare status, no stack trace), NODE_ENV=production
  in the systemd unit, /resume behind the admin session, constant-time
  compare of the internal IPC secret.
- Background-job "Check status" button gated like /status and scoped to its
  channel; status() reports the job's slug.
- File explorer share / send-to-me / preview / edit re-prove the opened file
  under the channel root (O_NOFOLLOW + /proc/self/fd), closing the
  symlink-swap race the agent-side export tools already closed.
- License card escapes platform-supplied text.
- npm audit fix: undici, zod, hono, fast-uri, body-parser, ip-address.

Tests: test/web-hardening.test.js (new), test/access-settings.test.js
(updated for the admin-only rule). FEATURES, TEST-PLAN and CHANGELOG updated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
… policy to Settings runtime controls

Security audit follow-up, batch 4 (Slack access consistency):

- Narrowing a channel's use policy from "approved" to "admins" or "none"
  now drops the approved members that were seeded into allowedUsers under
  the "approved" policy, keeping only explicit (un-approved) guests. Done
  inside the same metadata transaction by both the Slack Access control
  (save notice with the count, channel_access_members_evicted audit event)
  and the admin UI channel meta PUT. Widening back re-admits nobody by
  itself; the org-wide reset still clears the whole list. isAuthorized and
  the seeding under "approved" are unchanged.
- The Slack Settings runtime controls (channel engine / model / effort,
  thread pins, thread reset) follow the gateway's model-change policy like
  /model, the wizard, the shortcuts and the platform controls: a DM owner
  always; elsewhere a non-admin only under the "users" policy. The page
  renders the current values with the policy line instead of pickers, and
  the handlers refuse a crafted payload before any write.

Tests: test/access-policy-eviction-web.test.js and
test/settings-runtime-policy.test.js (new), test/access-settings.test.js
(appended), test/channel-settings-modal.test.js (runtime option now follows
the policy). FEATURES, TEST-PLAN and CHANGELOG updated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
…l failures

Security audit follow-up, batch 3. `clientKey` (the identity the admin login
and approval-link backoffs bucket on) used to believe CF-Connecting-IP and
the FIRST X-Forwarded-For hop for any request on a loopback socket — which is
every request behind nginx, caddy, cloudflared or ssh -L. A caller could mint
a fresh bucket per request by writing its own header: unlimited online
password guessing plus a free scrypt per attempt.

- New setting `trustProxy` (Settings → Connection → "Behind a reverse proxy";
  env CG_TRUST_PROXY; the setting wins): "" trusts nothing (the socket is the
  client), "cloudflare" reads CF-Connecting-IP only, "xff" reads the LAST
  X-Forwarded-For hop (the proxy's, never the client's first one). Legacy
  CG_TRUST_PROXY=1 maps to "xff"; unknown values trust nothing and the admin
  API refuses them with 400.
- createLoginLimiter gains a key-independent global cap (default 50 failures
  per 10 minutes → every attempt waits the maximum delay until the window
  drains), injectable for tests and switchable off.
- The /approve limiter inherits both through the shared helper. Nothing else
  authorizes on forwarded headers; the Secure-cookie decision still reads
  x-forwarded-proto (it only ever makes the cookie stricter).
- docs/OPERATIONS.md and INSTALL.md: declare the proxy, or all remote logins
  share one backoff bucket.

Tests: web-security (clientKey per mode, settings/env precedence, global
cap), settings-save-version (route validation + UI wiring),
approval-links (end-to-end through the approve router with and without a
declared proxy). FEATURES, TEST-PLAN, CHANGELOG updated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
…ons)

Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
…ns only or Locked

Only the seeded approved NON-admin members are evicted. Admins are admitted by
role under "Admins only", and under "Locked" the admin who locks the channel
must not lock themselves out of its Slack controls.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
A code channel is a dedicated Slack channel for one task, opened through
Slack's agents.conversations API only when the requester asks for it and
only after they confirm an approval card. It is registered as a child of
the origin channel: the origin's folder, access policy, secrets, skills,
engine and switches are re-projected onto it on every message and spawn,
while it keeps its own Slack identity, sessions and container.

The whole channel is one session (key `code-channel`, never a Slack ts),
so replies post top-level or in the thread a person used, the mention
gate admits every message there, the session status is addressed by
channel id without the legacy thread bridge, and a thread-less
agent_session_stopped sweeps everything running in the channel.

When the origin folder is a git repository the daemon creates branch
code/<slug> in a worktree at .worktrees/<slug> (from the upstream, else
the exact HEAD commit) and publishes its diff to the Code tab after each
turn, leaving out the gateway's managed instruction block; otherwise the
channel works in place. The requester may force either mode. The first
turn is started by the daemon with the task as the brief and the origin
thread as context. update_code_channel publishes html/block_kit/canvas/
pull_request tabs, context-bar items and the title; archive_code_channel
posts the wrap-up and archives behind an open-tier card.

Manifest: features.code_channels + the code_channels:manage scope.
Slack-only capability `codeChannels`; Slack-only guide reference.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
…sections)

Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
…es on public links

Security audit follow-up, batch 2.

Work-folder boundary (src/gateway/workdir-boundary.js): whatever the
allowlisted fsRoot admits, a channel's custom work folder may not be the
gateway account's home itself, the gateway runtime root or anything inside
it, ~/.ssh, ~/.claude, ~/.codex, the container engine's config or storage,
the hidden runtime artifact trees, the ChannelGate workspace root or one of
its platform folders, another conversation's work folder (default or
custom), or a parent of any of those. A subfolder of another conversation's
folder and the channel's own folders stay allowed. Enforced with a named
reason by set_channel_workdir, the admin UI workDir field and the runtime
resolver (an already-stored forbidden value fails every spawn closed with
the remedy); folder provisioning falls back to the default folder; one boot
warning per offending channel.

Protected export names: the shared export door (openConfinedFile) refuses a
hidden file or directory anywhere in the path, the managed .claude tree and
credential-shaped names, by requested AND physical path, for both public
link purposes, slack_share_file, Composio staging and previously minted
links. In-conversation previews (Slack explorer, Teams and Google Chat file
windows) are unchanged.

Two tests moved their custom work-folder fixtures beside the scratch gateway
root instead of under it, which is what the boundary now refuses.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
… door

Keep functionality admins rely on while closing the audit holes:

- Another conversation's CUSTOM work folder (two channels on one repository),
  its parent and its subfolders stay allowed; only the gateway-made default
  folders, the gateway's own trees and the home itself are refused.
- The protected-name export policy gets two strengths: strict for public
  links (hidden paths, managed instruction/memory files, credential-shaped
  and secret-mentioning names) and lenient for slack_share_file, the footer
  download, Composio staging and mail attachments (hidden paths and
  credential-shaped files only), so MEMORY.md or a token-usage report can
  still be sent where an authorized person already reaches.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
…c sections)

Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
tbiyss and others added 22 commits October 25, 2026 23:38
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Slack rejects /status in some app manifests as a built-in command. The
same handler now answers /status-cg, so a named agent's manifest can
register that name and keep the channel status report.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
# Conflicts:
#	CHANGELOG.md
#	FEATURES.md
#	TEST-PLAN.md
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
A code channel shares its origin's folder, so the shared-folder grant
guard compared the two channels' skill sets; catalog skills scoped to the
origin's channel id were missing on the child's id and the first live
open_code_channel failed after Slack had created the channel. Scoped
skills now resolve through the origin for a code channel.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
…l docs

Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>
Signed-off-by: Tiberiu Socaci <tsocaci@makeitfuture.com>

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants