Skip to content

Commit 734a4db

Browse files
committed
fix(worker): gate script sandbox diagnostics
Signed-off-by: zebbern <185730623+zebbern@users.noreply.github.com>
1 parent dde5bd4 commit 734a4db

4 files changed

Lines changed: 128 additions & 19 deletions

File tree

worker/src/components/core/__tests__/logic-script.test.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { afterEach, describe, expect, it, vi } from 'bun:test';
22
import { definition } from '../logic-script';
3-
import { extractPorts, type ExecutionContext } from '@sentris/component-sdk';
3+
import {
4+
extractPorts,
5+
type DockerRunnerConfig,
6+
type ExecutionContext,
7+
} from '@sentris/component-sdk';
48
import * as sdk from '@sentris/component-sdk';
59

610
// Mock context
@@ -24,6 +28,12 @@ const mockContext: ExecutionContext = {
2428
},
2529
};
2630

31+
function decodeGeneratedFile(command: string, filename: 'plugin.ts' | 'harness.ts'): string {
32+
const match = command.match(new RegExp(`echo "([^"]+)" \\| base64 -d > ${filename}`));
33+
expect(match).not.toBeNull();
34+
return Buffer.from(match![1], 'base64').toString('utf8');
35+
}
36+
2737
afterEach(() => {
2838
vi.restoreAllMocks();
2939
});
@@ -65,6 +75,50 @@ describe('Logic/Script Component', () => {
6575
expect(consoleLogSpy).not.toHaveBeenCalled();
6676
});
6777

78+
it('gates sandbox success diagnostics behind component debug logging', async () => {
79+
const previousDebugFlag = process.env.SENTRIS_DEBUG_COMPONENTS;
80+
delete process.env.SENTRIS_DEBUG_COMPONENTS;
81+
82+
const runSpy = vi.spyOn(sdk, 'runComponentWithRunner').mockResolvedValue({ sum: 3 });
83+
84+
try {
85+
await definition.execute(
86+
{
87+
inputs: {},
88+
params: {
89+
code: 'export async function script() { return { sum: 1 + 2 }; }',
90+
variables: [],
91+
returns: [{ name: 'sum', type: 'number' }],
92+
},
93+
},
94+
mockContext,
95+
);
96+
97+
const [runnerConfig] = runSpy.mock.calls[0] as [DockerRunnerConfig, ...unknown[]];
98+
const command = runnerConfig.command.join(' ');
99+
const pluginSource = decodeGeneratedFile(command, 'plugin.ts');
100+
const harnessSource = decodeGeneratedFile(command, 'harness.ts');
101+
102+
expect(runnerConfig.env).toEqual({ SENTRIS_DEBUG_COMPONENTS: '' });
103+
expect(pluginSource).toContain('SENTRIS_DEBUG_COMPONENTS');
104+
expect(harnessSource).toContain('SENTRIS_DEBUG_COMPONENTS');
105+
expect(pluginSource).not.toContain('console.log("[http-loader] Fetching:", href);');
106+
expect(harnessSource).not.toContain("console.log('[Script] Starting execution...');");
107+
expect(harnessSource).not.toContain(
108+
"console.log('[Script] Execution completed, writing output...');",
109+
);
110+
expect(harnessSource).not.toContain(
111+
"console.log('[Script] Output written to', OUTPUT_PATH);",
112+
);
113+
} finally {
114+
if (previousDebugFlag === undefined) {
115+
delete process.env.SENTRIS_DEBUG_COMPONENTS;
116+
} else {
117+
process.env.SENTRIS_DEBUG_COMPONENTS = previousDebugFlag;
118+
}
119+
}
120+
});
121+
68122
it('transpiles and executes TypeScript', async () => {
69123
vi.spyOn(sdk, 'runComponentWithRunner').mockResolvedValue({ msg: 'Value is 10' });
70124
const tsCode = `

worker/src/components/core/logic-script.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,14 @@ const rx_any = /./;
118118
const rx_http = /^https?:\\/\\//;
119119
const rx_path = /^\\.*\\//;
120120
121+
function debugLog(...args) {
122+
if (process.env.SENTRIS_DEBUG_COMPONENTS === "1") {
123+
console.log(...args);
124+
}
125+
}
126+
121127
async function load_http_module(href) {
122-
console.log("[http-loader] Fetching:", href);
128+
debugLog("[http-loader] Fetching:", href);
123129
const response = await fetch(href);
124130
const text = await response.text();
125131
if (response.ok) {
@@ -162,9 +168,15 @@ plugin({
162168
const harnessCode = `
163169
import { readFileSync, writeFileSync } from "node:fs";
164170
171+
function debugLog(...args) {
172+
if (process.env.SENTRIS_DEBUG_COMPONENTS === "1") {
173+
console.log(...args);
174+
}
175+
}
176+
165177
async function run() {
166178
try {
167-
console.log('[Script] Starting execution...');
179+
debugLog('[Script] Starting execution...');
168180
169181
// Read the combined payload from the mounted input file
170182
const inputPath = process.env.SENTRIS_INPUT_PATH || '/sentris-output/input.json';
@@ -191,7 +203,7 @@ async function run() {
191203
const { script } = await import("./user_script.ts");
192204
const result = await script(inputValues);
193205
194-
console.log('[Script] Execution completed, writing output...');
206+
debugLog('[Script] Execution completed, writing output...');
195207
const OUTPUT_PATH = process.env.SENTRIS_OUTPUT_PATH || '/sentris-output/result.json';
196208
const OUTPUT_DIR = OUTPUT_PATH.substring(0, OUTPUT_PATH.lastIndexOf('/'));
197209
@@ -201,7 +213,7 @@ async function run() {
201213
mkdirSync(OUTPUT_DIR, { recursive: true });
202214
}
203215
writeFileSync(OUTPUT_PATH, JSON.stringify(result || {}));
204-
console.log('[Script] Output written to', OUTPUT_PATH);
216+
debugLog('[Script] Output written to', OUTPUT_PATH);
205217
} catch (writeErr) {
206218
console.error('[Script] Failed to write output:', writeErr.message);
207219
throw writeErr;
@@ -306,7 +318,7 @@ const definition = defineComponent({
306318
...baseRunner,
307319
command: ['-c', shellCommand],
308320
env: {
309-
// No SENTRIS_INPUTS here to avoid E2BIG
321+
SENTRIS_DEBUG_COMPONENTS: process.env.SENTRIS_DEBUG_COMPONENTS ?? '',
310322
},
311323
};
312324

worker/src/temporal/activities/__tests__/webhook-parsing.activity.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ import { executeWebhookParsingScriptActivity } from '../webhook-parsing.activity
1818

1919
// ── Tests ────────────────────────────────────────────────────────────────────
2020

21+
const originalDebugWorkflow = process.env.SENTRIS_DEBUG_WORKFLOW;
22+
2123
describe('executeWebhookParsingScriptActivity', () => {
2224
beforeEach(() => {
25+
delete process.env.SENTRIS_DEBUG_WORKFLOW;
2326
mockRunComponentWithRunner.mockReset();
2427
mockCreateExecutionContext.mockClear();
2528
mockCreateExecutionContext.mockReturnValue({
@@ -124,4 +127,31 @@ describe('executeWebhookParsingScriptActivity', () => {
124127
const ctxArg = mockCreateExecutionContext.mock.calls[0][0];
125128
expect(ctxArg.runId).toContain('webhook-parse-');
126129
});
130+
131+
it('does not mirror parser info/debug collector logs to console by default', async () => {
132+
mockRunComponentWithRunner.mockResolvedValue({});
133+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
134+
const consoleDebugSpy = vi.spyOn(console, 'debug').mockImplementation(() => {});
135+
136+
try {
137+
await executeWebhookParsingScriptActivity({
138+
parsingScript: 'export async function script() { return {}; }',
139+
payload: {},
140+
headers: {},
141+
});
142+
143+
const ctxArg = mockCreateExecutionContext.mock.calls[0][0];
144+
ctxArg.logCollector({ level: 'info', message: 'parser started' });
145+
ctxArg.logCollector({ level: 'debug', message: 'parser details' });
146+
147+
expect(consoleLogSpy).not.toHaveBeenCalled();
148+
expect(consoleDebugSpy).not.toHaveBeenCalled();
149+
} finally {
150+
if (originalDebugWorkflow === undefined) {
151+
delete process.env.SENTRIS_DEBUG_WORKFLOW;
152+
} else {
153+
process.env.SENTRIS_DEBUG_WORKFLOW = originalDebugWorkflow;
154+
}
155+
}
156+
});
127157
});

worker/src/temporal/activities/webhook-parsing.activity.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import {
22
createExecutionContext,
33
runComponentWithRunner,
44
type DockerRunnerConfig,
5+
type LogEventInput,
56
} from '@sentris/component-sdk';
7+
import { workflowDiagnosticLog } from '../workflow-diagnostics';
68

79
export interface ExecuteWebhookParsingScriptActivityInput {
810
parsingScript: string;
@@ -11,6 +13,19 @@ export interface ExecuteWebhookParsingScriptActivityInput {
1113
timeoutSeconds?: number;
1214
}
1315

16+
function logWebhookParsingEntry(entry: LogEventInput): void {
17+
const message = `[Webhook Parse] ${entry.message}`;
18+
if (entry.level === 'error') {
19+
console.error(message);
20+
return;
21+
}
22+
if (entry.level === 'warn') {
23+
console.warn(message);
24+
return;
25+
}
26+
workflowDiagnosticLog(message);
27+
}
28+
1429
/**
1530
* Executes a user-supplied webhook parsing script inside a Bun Docker container.
1631
*
@@ -38,8 +53,14 @@ const rx_any = /./;
3853
const rx_http = /^https?:\\/\\//;
3954
const rx_path = /^\\.*\\//;
4055
56+
function debugLog(...args) {
57+
if (process.env.SENTRIS_DEBUG_WORKFLOW === "1") {
58+
console.log(...args);
59+
}
60+
}
61+
4162
async function load_http_module(href) {
42-
console.log("[http-loader] Fetching:", href);
63+
debugLog("[http-loader] Fetching:", href);
4364
const response = await fetch(href);
4465
const text = await response.text();
4566
if (response.ok) {
@@ -132,7 +153,9 @@ run();
132153
image: 'oven/bun:alpine',
133154
entrypoint: 'sh',
134155
command: ['-c', shellCommand],
135-
env: {},
156+
env: {
157+
SENTRIS_DEBUG_WORKFLOW: process.env.SENTRIS_DEBUG_WORKFLOW ?? '',
158+
},
136159
network: 'bridge',
137160
timeoutSeconds,
138161
stdinJson: false,
@@ -141,17 +164,7 @@ run();
141164
const context = createExecutionContext({
142165
runId: `webhook-parse-${Date.now()}`,
143166
componentRef: 'webhook.parse',
144-
logCollector: (entry) => {
145-
const log =
146-
entry.level === 'error'
147-
? console.error
148-
: entry.level === 'warn'
149-
? console.warn
150-
: entry.level === 'debug'
151-
? console.debug
152-
: console.log;
153-
log(`[Webhook Parse] ${entry.message}`);
154-
},
167+
logCollector: logWebhookParsingEntry,
155168
});
156169

157170
const params = {

0 commit comments

Comments
 (0)