Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
52 changes: 20 additions & 32 deletions src/server/production-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
isHostProjectExecutionOverrideEnabled,
} from "#veryfront/security/host-execution-policy.ts";
import { isSharedProjectRuntime } from "#veryfront/security/project-locality.ts";
import { runStartupDiscovery } from "./startup-discovery.ts";

const serverLog = logger.component("server");
const globalLog = logger.component("global");
Expand Down Expand Up @@ -225,6 +226,18 @@ export function startProductionServer(
// the actual data client-side after hydration.
enableSSRClientOnlyFetching();

// A dedicated single-project runtime carries the capability implicitly.
// A shared runtime intended to be the executor must be granted it by an
// operator, deliberately and visibly.
//
// Computed before discovery so startup and request handling share one
// value. They disagreed before issue-inbox#363: discovery hardcoded a
// grant while the handler computed the real posture.
const isolatedRuntimeGrant = bootstrap.config.fs?.veryfront?.proxyMode !== true &&
!isSharedProjectRuntime({ adapter });
const operatorGrant = isHostProjectExecutionOverrideEnabled();
const allowHostProjectCodeExecution = isolatedRuntimeGrant || operatorGrant;

// Run primitive discovery before serving (registries must be populated before first request)
if (discoveryConfig) {
try {
Expand All @@ -233,30 +246,12 @@ export function startProductionServer(
"#veryfront/platform/adapters/fs/wrapper.ts"
);

if (
discoveryConfig.projectSlug && discoveryConfig.apiToken &&
discoveryConfig.fsAdapter && isExtendedFSAdapter(discoveryConfig.fsAdapter) &&
discoveryConfig.fsAdapter.isMultiProjectMode()
) {
// Multi-project proxy: scope discovery to specific project
await discoveryConfig.fsAdapter.runWithContext(
discoveryConfig.projectSlug,
discoveryConfig.apiToken,
() =>
discoverAll({
baseDir: discoveryConfig.baseDir,
fsAdapter: discoveryConfig.fsAdapter,
verbose: discoveryConfig.verbose ?? false,
}),
);
} else {
await discoverAll({
baseDir: discoveryConfig.baseDir,
fsAdapter: discoveryConfig.fsAdapter,
verbose: discoveryConfig.verbose ?? false,
allowHostProjectCodeExecution: true,
});
}
await runStartupDiscovery({
config: discoveryConfig,
allowHostProjectCodeExecution,
discoverAll,
isExtendedFSAdapter,
});
} catch (error) {
serverLog.error("Primitive discovery failed", {
error: error instanceof Error ? error.message : String(error),
Expand All @@ -266,13 +261,6 @@ export function startProductionServer(

logger.info("Starting production server", { projectDir, port, bindAddress });

// A dedicated single-project runtime carries the capability implicitly.
// A shared runtime intended to be the executor must be granted it by an
// operator, deliberately and visibly.
const isolatedRuntimeGrant = bootstrap.config.fs?.veryfront?.proxyMode !== true &&
!isSharedProjectRuntime({ adapter });
const operatorGrant = isHostProjectExecutionOverrideEnabled();

if (operatorGrant && !isolatedRuntimeGrant) {
logger.warn("Shared runtime is executing tenant project code by operator grant", {
overrideEnv: HOST_PROJECT_EXECUTION_OVERRIDE_ENV,
Expand All @@ -289,7 +277,7 @@ export function startProductionServer(
defaultReleaseId,
defaultEnvironment,
localProjects,
allowHostProjectCodeExecution: isolatedRuntimeGrant || operatorGrant,
allowHostProjectCodeExecution,
});

const coreHandler = baseHandler;
Expand Down
89 changes: 89 additions & 0 deletions src/server/startup-discovery.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import "#veryfront/schemas/_test-setup.ts";
import { assertEquals } from "#veryfront/testing/assert.ts";
import { describe, it } from "#veryfront/testing/bdd.ts";
import type { DiscoveryConfig, DiscoveryResult } from "#veryfront/discovery/types.ts";
import type { FileSystemAdapter } from "#veryfront/platform/adapters/base.ts";
import type { ExtendedFileSystemAdapter } from "#veryfront/platform/adapters/fs/wrapper.ts";
import { runStartupDiscovery } from "./startup-discovery.ts";

function emptyResult(): DiscoveryResult {
return {
tools: new Map(),
agents: new Map(),
skills: new Map(),
resources: new Map(),
prompts: new Map(),
workflows: new Map(),
tasks: new Map(),
schedules: new Map(),
webhooks: new Map(),
evals: new Map(),
errors: [],
};
}

function recorder() {
const calls: DiscoveryConfig[] = [];
return {
calls,
discoverAll: (config: DiscoveryConfig) => {
calls.push(config);
return Promise.resolve(emptyResult());
},
};
}
Comment thread
kojiwakayama marked this conversation as resolved.

/** No adapter is extended, so discovery takes the unscoped branch. */
const noExtendedAdapters = (_fs: FileSystemAdapter): _fs is ExtendedFileSystemAdapter => false;

/** Every adapter is extended, so discovery takes the scoped branch. */
const allExtendedAdapters = (_fs: FileSystemAdapter): _fs is ExtendedFileSystemAdapter => true;

describe("server/startup-discovery", () => {
it("denies host execution when the deployment does not grant it", async () => {
const { calls, discoverAll } = recorder();

await runStartupDiscovery({
config: { baseDir: "/app" },
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
allowHostProjectCodeExecution: false,
discoverAll,
isExtendedFSAdapter: noExtendedAdapters,
});

assertEquals(calls.length, 1);
assertEquals(calls[0]?.allowHostProjectCodeExecution, false);
});

it("grants host execution when the deployment does", async () => {
const { calls, discoverAll } = recorder();

await runStartupDiscovery({
config: { baseDir: "/app" },
allowHostProjectCodeExecution: true,
discoverAll,
isExtendedFSAdapter: noExtendedAdapters,
});

assertEquals(calls[0]?.allowHostProjectCodeExecution, true);
});

it("keeps the scoped multi-project path ungranted", async () => {
const { calls, discoverAll } = recorder();
const fsAdapter = {
isMultiProjectMode: () => true,
runWithContext: <T>(_slug: string, _token: string, fn: () => Promise<T>) => fn(),
} as unknown as ExtendedFileSystemAdapter;

await runStartupDiscovery({
config: { baseDir: "/app", projectSlug: "p", apiToken: "t", fsAdapter },
// Even with the deployment granting, the scoped branch must not pass it:
// that path evaluates tenant source under a project context.
allowHostProjectCodeExecution: true,
discoverAll,
isExtendedFSAdapter: allExtendedAdapters,
});

assertEquals(calls.length, 1);
assertEquals(calls[0]?.allowHostProjectCodeExecution, undefined);
});
});
67 changes: 67 additions & 0 deletions src/server/startup-discovery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Primitive discovery run once at startup, before the server accepts requests.
*
* Extracted from `production-server.ts` so the host-execution grant it passes
* can be tested. The bug this addresses (issue-inbox#363) was that the fallback
* branch hardcoded `allowHostProjectCodeExecution: true` while the request
* handler computed the real answer fifteen lines below, so a deployment that
* denied execution at request time still granted it at startup.
*
* Same shape as veryfront-code#3364, where `api-handler-wrapper.ts` passed a
* hardcoded `true` and made the computed predicate dead code. A hardcoded
* capability sitting next to a computed one is the pattern to look for.
*/

import type { DiscoveryConfig, DiscoveryResult } from "#veryfront/discovery/types.ts";
import type { FileSystemAdapter } from "#veryfront/platform/adapters/base.ts";
import type { ExtendedFileSystemAdapter } from "#veryfront/platform/adapters/fs/wrapper.ts";
import type { DiscoveryOptions } from "./production-server.ts";

export interface RunStartupDiscoveryInput {
config: DiscoveryOptions;
/**
* The deployment's posture, computed once by the host-owned entrypoint and
* shared with the request handler. Never hardcoded here.
*/
allowHostProjectCodeExecution: boolean;
discoverAll: (config: DiscoveryConfig) => Promise<DiscoveryResult>;
isExtendedFSAdapter: (fs: FileSystemAdapter) => fs is ExtendedFileSystemAdapter;
}

/** Whether discovery can be scoped to one project on a multi-project adapter. */
function scopedAdapter(
input: RunStartupDiscoveryInput,
): ExtendedFileSystemAdapter | undefined {
const { config } = input;
if (!config.projectSlug || !config.apiToken || !config.fsAdapter) return undefined;
if (!input.isExtendedFSAdapter(config.fsAdapter)) return undefined;
return config.fsAdapter.isMultiProjectMode() ? config.fsAdapter : undefined;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

export async function runStartupDiscovery(input: RunStartupDiscoveryInput): Promise<void> {
const { config } = input;
const base = {
baseDir: config.baseDir,
fsAdapter: config.fsAdapter,
verbose: config.verbose ?? false,
};

const adapter = scopedAdapter(input);
if (adapter) {
// Scoped to one project, so tenant source is in reach. This path stays
// ungranted whatever the deployment's posture: the capability is for a
// host-owned entrypoint evaluating its own project, not for discovery
// running inside a tenant's context.
await adapter.runWithContext(
config.projectSlug as string,
config.apiToken as string,
() => input.discoverAll(base),
);
Comment thread
kojiwakayama marked this conversation as resolved.
Outdated
return;
}

await input.discoverAll({
...base,
allowHostProjectCodeExecution: input.allowHostProjectCodeExecution,
});
}
Loading