Move secrets between users and computers without the model seeing them

A new personal env scope (paradigm user: one user's secret for one bot)
beats every other scope, reaches only that user's runs of that bot, and
is redacted like any secret. Bots take secrets in with request_secret,
which puts a [secret-request:ID] card in the chat: the user types the
value into a masked input there, never into a message, and the bot is
told only the key. Bots show secrets with capture_secret (a task's
computer tool that saves a file or a command's output straight to a
personal env, then redacts it from the rest of the run) and show_secret
(an env by key): both give a [secret:ID] chip whose Reveal and Copy fetch
the value on click, for its own user (personal) or a super admin
(anything else), logged by key. Values never go into messages, page
props, history, Copy chat or logs. Users manage their own secrets on
Settings > Secrets; the Environment page lists everyone's, remove only.
Deleting a bot or user removes its personal secrets, and SecretsNote
tells bots never to ask for or print secrets in chat.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-25 16:51:32 +01:00
co-authored by Claude Opus 5.5
parent 83ff79b9c3
commit 69988e08fc
78 changed files with 2472 additions and 52 deletions
+77 -3
View File
@@ -33,7 +33,7 @@ import {
BotFileSources,
} from "@/src/dict/bot-file-dict";
import type { BUN_SQLITE_DatabaseSchemaType } from "@moduletrace/bun-sqlite/types";
import { EnvParadigms } from "@/src/dict/env-dict";
import { EnvParadigms, SecretRequestStatuses } from "@/src/dict/env-dict";
import { McpServerStatuses, McpTransports } from "@/src/dict/mcp-dict";
import {
CustomToolKinds,
@@ -1431,6 +1431,68 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
},
],
},
{
tableName: "secret_requests",
tableDescription: `A secret (a password, token or key) a bot asked a user for in their conversation. The user types it into the request's card, never into the chat, so the model never sees it: it's saved as their personal env (paradigm user) for that bot, which reaches that user's runs on computers by key`,
fields: [
{
fieldName: "key",
fieldDescription: `The env key it's saved under`,
dataType: "TEXT",
},
{
fieldName: "description",
fieldDescription: `What it is, saved as the env's description`,
dataType: "TEXT",
},
{
fieldName: "reason",
fieldDescription: `Why the bot asks, in its own words, shown on the card as such`,
dataType: "TEXT",
},
{
fieldName: "status",
dataType: "TEXT",
options: [...SecretRequestStatuses],
defaultValue: "pending",
},
{
fieldName: "env_id",
fieldDescription: `The env it saved, once saved. Not a foreign key`,
dataType: "INTEGER",
},
{
fieldName: "decided_at",
dataType: "INTEGER",
},
{
fieldName: "user_id",
fieldDescription: `The user it was asked of, who alone answers it. Not a foreign key`,
dataType: "INTEGER",
},
{
fieldName: "bot_id",
fieldDescription: `The bot that asked, whose runs for that user get it. Not a foreign key`,
dataType: "INTEGER",
},
{
fieldName: "session_id",
fieldDescription: `The conversation it was asked in. Not a foreign key`,
dataType: "INTEGER",
},
{
fieldName: "workspace_id",
fieldDescription: `The session's workspace; null for the bot's own chat. Not a foreign key`,
dataType: "INTEGER",
},
],
indexes: [
{
indexName: "idx_secret_requests_session_id",
indexTableFields: ["session_id"],
},
],
},
{
tableName: "workspaces",
fields: [
@@ -3086,7 +3148,7 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
},
{
fieldName: "paradigm",
fieldDescription: `Where it applies: general, a workspace, a computer, or a single bot. A bot's own beats the computer, which beats the workspace, which beats general`,
fieldDescription: `Where it applies: general, a workspace, a computer, a single bot, or one user's runs of one bot (user, set from the chat). A user's own beats the bot's, which beats the computer, which beats the workspace, which beats general`,
dataType: "TEXT",
options: EnvParadigms.map((p) => p.value),
defaultValue: "general",
@@ -3117,7 +3179,7 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
},
{
fieldName: "bot_id",
fieldDescription: `The bot, for paradigm bot. Goes with the bot`,
fieldDescription: `The bot, for paradigm bot or user. Goes with the bot`,
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
@@ -3127,6 +3189,18 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
foreignKeyName: "envs_bot_fk",
},
},
{
fieldName: "user_id",
fieldDescription: `The user, for paradigm user: only their runs of \`bot_id\` get it, and only they may reveal it. Goes with the user`,
dataType: "INTEGER",
foreignKey: {
cascadeDelete: true,
destinationTableName: "users",
destinationTableColumnName: "id",
destinationTableColumnType: "INTEGER",
foreignKeyName: "envs_user_fk",
},
},
{
fieldName: "secret",
fieldDescription: `1 hides the value once saved (only replaced, never shown) and redacts it from everything bots read`,
+58 -4
View File
@@ -20,6 +20,7 @@ export const BunSQLiteTables = [
"bot_hooks",
"bot_hook_events",
"hook_proposals",
"secret_requests",
"workspaces",
"workspace_bots",
"bot_types",
@@ -896,6 +897,55 @@ export type BUN_SQLITE_COBBLE_HOOK_PROPOSALS = {
workspace_id?: number | "";
}
export type BUN_SQLITE_COBBLE_SECRET_REQUESTS = {
/**
* The unique identifier of the record.
*/
id?: number | "";
/**
* The time when the record was created. (Unix Timestamp)
*/
created_at?: number | "";
/**
* The time when the record was updated. (Unix Timestamp)
*/
updated_at?: number | "";
/**
* The env key it's saved under
*/
key?: string;
/**
* What it is, saved as the env's description
*/
description?: string;
/**
* Why the bot asks, in its own words, shown on the card as such
*/
reason?: string;
status?: "pending" | "saved" | "declined" | "";
/**
* The env it saved, once saved. Not a foreign key
*/
env_id?: number | "";
decided_at?: number | "";
/**
* The user it was asked of, who alone answers it. Not a foreign key
*/
user_id?: number | "";
/**
* The bot that asked, whose runs for that user get it. Not a foreign key
*/
bot_id?: number | "";
/**
* The conversation it was asked in. Not a foreign key
*/
session_id?: number | "";
/**
* The session's workspace; null for the bot's own chat. Not a foreign key
*/
workspace_id?: number | "";
}
export type BUN_SQLITE_COBBLE_WORKSPACES = {
/**
* The unique identifier of the record.
@@ -1895,9 +1945,9 @@ export type BUN_SQLITE_COBBLE_ENVS = {
*/
description?: string;
/**
* Where it applies: general, a workspace, a computer, or a single bot. A bot's own beats the computer, which beats the workspace, which beats general
* Where it applies: general, a workspace, a computer, a single bot, or one user's runs of one bot (user, set from the chat). A user's own beats the bot's, which beats the computer, which beats the workspace, which beats general
*/
paradigm?: "general" | "workspace" | "computer" | "bot" | "";
paradigm?: "general" | "workspace" | "computer" | "bot" | "user" | "";
/**
* The workspace, for paradigm workspace
*/
@@ -1907,9 +1957,13 @@ export type BUN_SQLITE_COBBLE_ENVS = {
*/
computer_id?: number | "";
/**
* The bot, for paradigm bot. Goes with the bot
* The bot, for paradigm bot or user. Goes with the bot
*/
bot_id?: number | "";
/**
* The user, for paradigm user: only their runs of `bot_id` get it, and only they may reveal it. Goes with the user
*/
user_id?: number | "";
/**
* 1 hides the value once saved (only replaced, never shown) and redacts it from everything bots read
*/
@@ -2167,4 +2221,4 @@ export type BUN_SQLITE_COBBLE_APP_SETTINGS = {
value?: string;
}
export type BUN_SQLITE_COBBLE_ALL_TYPEDEFS = BUN_SQLITE_COBBLE_USERS & BUN_SQLITE_COBBLE_USER_TYPES & BUN_SQLITE_COBBLE_SSO_LOGIN_CODES & BUN_SQLITE_COBBLE_USER_PASSWORDS & BUN_SQLITE_COBBLE_MEDIA & BUN_SQLITE_COBBLE_MEDIA_PARADIGMS & BUN_SQLITE_COBBLE_BOTS & BUN_SQLITE_COBBLE_BOT_SESSIONS & BUN_SQLITE_COBBLE_BOT_MESSAGES & BUN_SQLITE_COBBLE_BOT_MESSAGE_REACTIONS & BUN_SQLITE_COBBLE_BOT_TASKS & BUN_SQLITE_COBBLE_BOT_TASK_STEPS & BUN_SQLITE_COBBLE_TASKS & BUN_SQLITE_COBBLE_TASK_MEDIA & BUN_SQLITE_COBBLE_TASK_NOTES & BUN_SQLITE_COBBLE_BOTS_QUEUE & BUN_SQLITE_COBBLE_BOTS_QUEUE_UPDATES & BUN_SQLITE_COBBLE_CRON_JOBS & BUN_SQLITE_COBBLE_BOT_HOOKS & BUN_SQLITE_COBBLE_BOT_HOOK_EVENTS & BUN_SQLITE_COBBLE_HOOK_PROPOSALS & BUN_SQLITE_COBBLE_WORKSPACES & BUN_SQLITE_COBBLE_WORKSPACE_BOTS & BUN_SQLITE_COBBLE_BOT_TYPES & BUN_SQLITE_COBBLE_WORKSPACE_USERS & BUN_SQLITE_COBBLE_BOT_USERS & BUN_SQLITE_COBBLE_BOT_USER_MEMORIES & BUN_SQLITE_COBBLE_BOT_USER_FACES & BUN_SQLITE_COBBLE_BOT_MESSAGE_MEDIA & BUN_SQLITE_COBBLE_CONTAINERS & BUN_SQLITE_COBBLE_COMPUTERS & BUN_SQLITE_COBBLE_BOT_COMPUTERS & BUN_SQLITE_COBBLE_WORKSPACE_COMPUTERS & BUN_SQLITE_COBBLE_HARNESS_PROVIDERS & BUN_SQLITE_COBBLE_HARNESS_MODELS & BUN_SQLITE_COBBLE_HARNESS_SETTINGS & BUN_SQLITE_COBBLE_BOT_HARNESS_SETTINGS & BUN_SQLITE_COBBLE_WORKSPACE_HARNESS_SETTINGS & BUN_SQLITE_COBBLE_MCP_SERVERS & BUN_SQLITE_COBBLE_CUSTOM_TOOLS & BUN_SQLITE_COBBLE_SKILLS & BUN_SQLITE_COBBLE_EXTENSION_PROPOSALS & BUN_SQLITE_COBBLE_BOT_MCP_SERVERS & BUN_SQLITE_COBBLE_WORKSPACE_MCP_SERVERS & BUN_SQLITE_COBBLE_BOT_CUSTOM_TOOLS & BUN_SQLITE_COBBLE_WORKSPACE_CUSTOM_TOOLS & BUN_SQLITE_COBBLE_BOT_SKILLS & BUN_SQLITE_COBBLE_WORKSPACE_SKILLS & BUN_SQLITE_COBBLE_LOGS & BUN_SQLITE_COBBLE_USER_PRESENCE & BUN_SQLITE_COBBLE_PUSH_SUBSCRIPTIONS & BUN_SQLITE_COBBLE_ENVS & BUN_SQLITE_COBBLE_BOT_FILES & BUN_SQLITE_COBBLE_BOT_FILE_CHUNKS & BUN_SQLITE_COBBLE_TOKEN_USAGES & BUN_SQLITE_COBBLE_BOT_FILE_VECTORS & BUN_SQLITE_COBBLE_FILE_INGEST_QUEUE & BUN_SQLITE_COBBLE_APP_SETTINGS
export type BUN_SQLITE_COBBLE_ALL_TYPEDEFS = BUN_SQLITE_COBBLE_USERS & BUN_SQLITE_COBBLE_USER_TYPES & BUN_SQLITE_COBBLE_SSO_LOGIN_CODES & BUN_SQLITE_COBBLE_USER_PASSWORDS & BUN_SQLITE_COBBLE_MEDIA & BUN_SQLITE_COBBLE_MEDIA_PARADIGMS & BUN_SQLITE_COBBLE_BOTS & BUN_SQLITE_COBBLE_BOT_SESSIONS & BUN_SQLITE_COBBLE_BOT_MESSAGES & BUN_SQLITE_COBBLE_BOT_MESSAGE_REACTIONS & BUN_SQLITE_COBBLE_BOT_TASKS & BUN_SQLITE_COBBLE_BOT_TASK_STEPS & BUN_SQLITE_COBBLE_TASKS & BUN_SQLITE_COBBLE_TASK_MEDIA & BUN_SQLITE_COBBLE_TASK_NOTES & BUN_SQLITE_COBBLE_BOTS_QUEUE & BUN_SQLITE_COBBLE_BOTS_QUEUE_UPDATES & BUN_SQLITE_COBBLE_CRON_JOBS & BUN_SQLITE_COBBLE_BOT_HOOKS & BUN_SQLITE_COBBLE_BOT_HOOK_EVENTS & BUN_SQLITE_COBBLE_HOOK_PROPOSALS & BUN_SQLITE_COBBLE_SECRET_REQUESTS & BUN_SQLITE_COBBLE_WORKSPACES & BUN_SQLITE_COBBLE_WORKSPACE_BOTS & BUN_SQLITE_COBBLE_BOT_TYPES & BUN_SQLITE_COBBLE_WORKSPACE_USERS & BUN_SQLITE_COBBLE_BOT_USERS & BUN_SQLITE_COBBLE_BOT_USER_MEMORIES & BUN_SQLITE_COBBLE_BOT_USER_FACES & BUN_SQLITE_COBBLE_BOT_MESSAGE_MEDIA & BUN_SQLITE_COBBLE_CONTAINERS & BUN_SQLITE_COBBLE_COMPUTERS & BUN_SQLITE_COBBLE_BOT_COMPUTERS & BUN_SQLITE_COBBLE_WORKSPACE_COMPUTERS & BUN_SQLITE_COBBLE_HARNESS_PROVIDERS & BUN_SQLITE_COBBLE_HARNESS_MODELS & BUN_SQLITE_COBBLE_HARNESS_SETTINGS & BUN_SQLITE_COBBLE_BOT_HARNESS_SETTINGS & BUN_SQLITE_COBBLE_WORKSPACE_HARNESS_SETTINGS & BUN_SQLITE_COBBLE_MCP_SERVERS & BUN_SQLITE_COBBLE_CUSTOM_TOOLS & BUN_SQLITE_COBBLE_SKILLS & BUN_SQLITE_COBBLE_EXTENSION_PROPOSALS & BUN_SQLITE_COBBLE_BOT_MCP_SERVERS & BUN_SQLITE_COBBLE_WORKSPACE_MCP_SERVERS & BUN_SQLITE_COBBLE_BOT_CUSTOM_TOOLS & BUN_SQLITE_COBBLE_WORKSPACE_CUSTOM_TOOLS & BUN_SQLITE_COBBLE_BOT_SKILLS & BUN_SQLITE_COBBLE_WORKSPACE_SKILLS & BUN_SQLITE_COBBLE_LOGS & BUN_SQLITE_COBBLE_USER_PRESENCE & BUN_SQLITE_COBBLE_PUSH_SUBSCRIPTIONS & BUN_SQLITE_COBBLE_ENVS & BUN_SQLITE_COBBLE_BOT_FILES & BUN_SQLITE_COBBLE_BOT_FILE_CHUNKS & BUN_SQLITE_COBBLE_TOKEN_USAGES & BUN_SQLITE_COBBLE_BOT_FILE_VECTORS & BUN_SQLITE_COBBLE_FILE_INGEST_QUEUE & BUN_SQLITE_COBBLE_APP_SETTINGS