Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
5c1a323
feat(secrets): add non-secret workspace variables
mzxchandra Aug 7, 2026
1ae1033
feat(copilot): consume the non-secret env var contract
mzxchandra Aug 7, 2026
fb850c8
test(secrets): pin that the non-secret exemption is inert and surgical
mzxchandra Aug 7, 2026
bfd7825
fix(secrets): gate the non-secret bypass on workspace access
mzxchandra Aug 7, 2026
b20f8aa
feat(secrets): split the secrets page into secrets and variables sect…
mzxchandra Aug 7, 2026
633eb5e
Merge remote-tracking branch 'origin/staging' into feat/workspace-var…
mzxchandra Aug 7, 2026
1373fe2
fix(secrets): use getErrorMessage for the visibility-change toast
mzxchandra Aug 7, 2026
686e61c
fix(secrets): authorize visibility changes before any write
mzxchandra Aug 7, 2026
2fac109
fix(secrets): re-decide visibility authorization at write time
mzxchandra Aug 7, 2026
feb112c
Merge remote-tracking branch 'origin/staging' into feat/workspace-var…
mzxchandra Aug 7, 2026
b552c94
fix(secrets): make env writes and their authorization one transaction
mzxchandra Aug 7, 2026
40c5188
fix(secrets): lock the org-admin grant too, and read auth on the tx
mzxchandra Aug 7, 2026
2e771db
fix(secrets): show a renamed variable as a variable before save
mzxchandra Aug 7, 2026
626d845
fix(secrets): let server visibility outrank the carried rename
mzxchandra Aug 7, 2026
44f331a
fix(secrets): never send carried rename visibility for a known key
mzxchandra Aug 7, 2026
d991a08
fix(db): add the visibility constraint NOT VALID, then validate
mzxchandra Aug 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions apps/sim/app/api/knowledge/search/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ import {
} from '@/app/api/knowledge/search/utils'
import { createKnowledgeRegistryResponse } from '@/app/api/knowledge/secret-provenance'
import { checkKnowledgeBaseAccess, type KnowledgeBaseAccessResult } from '@/app/api/knowledge/utils'
import { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry'
import {
EMPTY_NON_SECRET_NAMES,
ResolvedSecretTraceRegistry,
} from '@/executor/utils/resolved-secret-trace-registry'
import { getRerankModelPricing } from '@/providers/models'
import { calculateCost } from '@/providers/utils'

Expand Down Expand Up @@ -372,10 +375,14 @@ export const POST = withRouteHandler(async (request: NextRequest) => {

const resultSecretRegistry =
modelInputProvenance.registry ??
new ResolvedSecretTraceRegistry([], {
userId,
...(workspaceId ? { workspaceId } : {}),
})
new ResolvedSecretTraceRegistry(
[],
{
userId,
...(workspaceId ? { workspaceId } : {}),
},
EMPTY_NON_SECRET_NAMES
)
const resultProvenanceSnapshot = await importKnowledgeSearchResultSecretProvenance({
registry: resultSecretRegistry,
results,
Expand Down
13 changes: 9 additions & 4 deletions apps/sim/app/api/knowledge/search/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vites
import { env } from '@/lib/core/config/env'
import * as documentsUtilsModule from '@/lib/knowledge/documents/utils'
import { runWithKnowledgeModelInputProvenance } from '@/lib/knowledge/model-input-provenance'
import { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry'
import {
EMPTY_NON_SECRET_NAMES,
ResolvedSecretTraceRegistry,
} from '@/executor/utils/resolved-secret-trace-registry'

/**
* Spy on the real documents/utils namespace instead of vi.mock: the shared
Expand Down Expand Up @@ -804,9 +807,11 @@ describe('Knowledge Search Utils', () => {
},
})

const registry = new ResolvedSecretTraceRegistry([
{ name: 'TOKEN', plaintext: 'secret-value', encryptedValue: 'encrypted-token' },
])
const registry = new ResolvedSecretTraceRegistry(
[{ name: 'TOKEN', plaintext: 'secret-value', encryptedValue: 'encrypted-token' }],
undefined,
EMPTY_NON_SECRET_NAMES
)
registry.recordResolved('TOKEN', 'secret-value')

await runWithKnowledgeModelInputProvenance(registry, () =>
Expand Down
29 changes: 20 additions & 9 deletions apps/sim/app/api/knowledge/secret-provenance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ import {
knowledgeDocumentTagValueSelectionKey,
parseKnowledgeDocumentTagProvenanceTargets,
} from '@/lib/knowledge/secret-provenance-selection'
import { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry'
import {
EMPTY_NON_SECRET_NAMES,
ResolvedSecretTraceRegistry,
} from '@/executor/utils/resolved-secret-trace-registry'

function invalidKnowledgeProvenanceResponse(): NextResponse {
return NextResponse.json({ error: 'Invalid knowledge secret provenance' }, { status: 400 })
Expand Down Expand Up @@ -151,10 +154,14 @@ export async function createKnowledgeProvenanceResponse(options: {
)
if (negotiation.status === 'not-requested') return NextResponse.json(options.body)
if (negotiation.status === 'rejected') return invalidKnowledgeProvenanceResponse()
const registry = new ResolvedSecretTraceRegistry([], {
userId: options.userId,
...(options.workspaceId ? { workspaceId: options.workspaceId } : {}),
})
const registry = new ResolvedSecretTraceRegistry(
[],
{
userId: options.userId,
...(options.workspaceId ? { workspaceId: options.workspaceId } : {}),
},
EMPTY_NON_SECRET_NAMES
)
for (const provenance of options.provenances) {
if (provenance.status === 'unknown') {
registry.markIncomplete()
Expand Down Expand Up @@ -225,10 +232,14 @@ export async function createKnowledgePersistedResponse(options: {
if (negotiation.status === 'not-requested') return NextResponse.json(options.body)
if (negotiation.status === 'rejected') return invalidKnowledgeProvenanceResponse()

const registry = new ResolvedSecretTraceRegistry([], {
userId: options.userId,
...(options.workspaceId ? { workspaceId: options.workspaceId } : {}),
})
const registry = new ResolvedSecretTraceRegistry(
[],
{
userId: options.userId,
...(options.workspaceId ? { workspaceId: options.workspaceId } : {}),
},
EMPTY_NON_SECRET_NAMES
)
await importKnowledgePersistedResponseSecretProvenance({
registry,
documents: options.documents,
Expand Down
7 changes: 5 additions & 2 deletions apps/sim/app/api/mcp/serve/[serverId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ import {
import { getMeaningfulWorkflowDescription } from '@/lib/mcp/workflow-tool-schema'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
import { projectResolvedSecretModelContent } from '@/executor/utils/resolved-secret-content-projection'
import { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry'
import {
EMPTY_NON_SECRET_NAMES,
ResolvedSecretTraceRegistry,
} from '@/executor/utils/resolved-secret-trace-registry'

const logger = createLogger('WorkflowMcpServeAPI')
const MAX_MCP_SERVE_BODY_BYTES = 10 * 1024 * 1024
Expand Down Expand Up @@ -286,7 +289,7 @@ async function projectWorkflowMcpModelContent(
privateProvenance: unknown,
scope: { userId: string; workspaceId: string }
): Promise<unknown> {
const registry = new ResolvedSecretTraceRegistry([], scope)
const registry = new ResolvedSecretTraceRegistry([], scope, EMPTY_NON_SECRET_NAMES)
const imported = await registry.importCrossingProvenance(privateProvenance, value, {
trusted: true,
})
Expand Down
17 changes: 12 additions & 5 deletions apps/sim/app/api/memory/secret-provenance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import {
serializePrivateToolMetadataResponseEnvelope,
} from '@/lib/execution/private-tool-metadata'
import { readBoundMemorySecretProvenance } from '@/lib/memory/secret-provenance'
import { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry'
import {
EMPTY_NON_SECRET_NAMES,
ResolvedSecretTraceRegistry,
} from '@/executor/utils/resolved-secret-trace-registry'

const MAX_PRIVATE_MEMORY_CROSSINGS = 10_000
const PRIVATE_MEMORY_QUERY_CHUNK_SIZE = 8
Expand Down Expand Up @@ -91,10 +94,14 @@ export async function createMemoryResponse(options: {
if (negotiation.status === 'not-requested') return NextResponse.json(options.body)
if (negotiation.status === 'rejected') return invalidMemoryProvenanceResponse()

const registry = new ResolvedSecretTraceRegistry([], {
userId: options.userId,
workspaceId: options.workspaceId,
})
const registry = new ResolvedSecretTraceRegistry(
[],
{
userId: options.userId,
workspaceId: options.workspaceId,
},
EMPTY_NON_SECRET_NAMES
)
if (options.memories.length > MAX_PRIVATE_MEMORY_CROSSINGS) {
registry.markIncomplete()
} else {
Expand Down
17 changes: 12 additions & 5 deletions apps/sim/app/api/workflows/[id]/log/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import { validateWorkflowAccess } from '@/app/api/workflows/middleware'
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
import type { SerializableExecutionState } from '@/executor/execution/types'
import type { ExecutionResult } from '@/executor/types'
import { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry'
import {
EMPTY_NON_SECRET_NAMES,
ResolvedSecretTraceRegistry,
} from '@/executor/utils/resolved-secret-trace-registry'

const logger = createLogger('WorkflowLogAPI')

Expand Down Expand Up @@ -122,10 +125,14 @@ export const POST = withRouteHandler(
const isChatExecution = result.metadata?.source === 'chat'
const triggerType = isChatExecution ? 'chat' : 'manual'
const loggingSession = new LoggingSession(id, executionId, triggerType, requestId)
const resolvedSecretTraceRegistry = new ResolvedSecretTraceRegistry([], {
userId: actorUserId,
workspaceId: existingLog.workspaceId,
})
const resolvedSecretTraceRegistry = new ResolvedSecretTraceRegistry(
[],
{
userId: actorUserId,
workspaceId: existingLog.workspaceId,
},
EMPTY_NON_SECRET_NAMES
)
const trustedExecutionData = await materializeExecutionData(
existingLog.executionData as Record<string, unknown>,
{
Expand Down
Loading
Loading