Browse Source
Four test cases under describe('js-executor'):
- sandbox isolates args from host realm (JVN#16937365 — regression guard)
- sandbox passes string args through unchanged
- non-sandbox path does not isolate from host realm (documented contract)
- non-sandbox path passes string args through unchanged
Tests use Node's built-in node:test + node:assert (zero new devDependencies;
ts-node was already there). Two npm scripts:
test — spec output for local dev
test:ci — spec to stdout + Node's built-in junit reporter to
target/surefire-reports/TEST-js-executor.xml
Wired 'yarn test:ci' into the Maven 'test' phase via frontend-maven-plugin,
so 'mvn test -pl=msa/js-executor' produces JUnit XML that TeamCity's
Maven runner auto-discovers under the 'js-executor' suite name.
TEST_FAST.md picks up the same step.
tsconfig excludes test/ from the production pkg bundle.
pull/15600/head
5 changed files with 97 additions and 2 deletions
@ -0,0 +1,83 @@ |
|||
///
|
|||
/// Copyright © 2016-2026 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { describe, test } from 'node:test'; |
|||
import assert from 'node:assert/strict'; |
|||
import { JsExecutor } from '../api/jsExecutor'; |
|||
|
|||
// describe('js-executor') groups all cases under <testsuite name="js-executor">
|
|||
// in the JUnit XML so they show up under that suite in TeamCity's Tests tab,
|
|||
// alongside thousands of Java tests.
|
|||
describe('js-executor', () => { |
|||
|
|||
test('sandbox isolates args from host realm (JVN#16937365)', async () => { |
|||
const exec = new JsExecutor(true); |
|||
const script = await exec.compileScript(`function(msg, metadata, msgType){
|
|||
var F = args.constructor.constructor; |
|||
var p = F("return process")(); |
|||
return p && p.mainModule ? 'reached-host' : 'isolated'; |
|||
}`);
|
|||
await assert.rejects( |
|||
exec.executeScript(script, ['{}', '{}', 'POST_TELEMETRY_REQUEST'], 5000), |
|||
/process is not defined/, |
|||
'host process must not be reachable from inside the sandbox', |
|||
); |
|||
}); |
|||
|
|||
test('sandbox passes string args through unchanged', async () => { |
|||
const exec = new JsExecutor(true); |
|||
const script = await exec.compileScript(`function(msg, metadata, msgType){
|
|||
return { msgIsString: typeof msg === 'string', count: args.length, first: args[0] }; |
|||
}`);
|
|||
const out = await exec.executeScript(script, ['hello', '{}', 'X'], 5000); |
|||
// Field-by-field: the returned object is owned by the sandbox realm, so
|
|||
// its prototype is not the host Object.prototype and deepStrictEqual would
|
|||
// reject it on prototype mismatch even when the values match.
|
|||
assert.equal(out.msgIsString, true); |
|||
assert.equal(out.count, 3); |
|||
assert.equal(out.first, 'hello'); |
|||
}); |
|||
|
|||
// The use_sandbox=false path is intentionally non-isolating: scripts compile
|
|||
// and run in the host realm via vm.compileFunction. The two tests below codify
|
|||
// that documented contract so any future behavior change shows up as a test
|
|||
// failure and forces a deliberate update of the docs and threat model.
|
|||
|
|||
test('non-sandbox path does not isolate from host realm (documented contract)', async () => { |
|||
const exec = new JsExecutor(false); |
|||
const script = await exec.compileScript(`function(msg, metadata, msgType){
|
|||
// Non-destructive host-reach probe: typeof process.platform is 'string'
|
|||
// only if the host process object is reachable.
|
|||
var F = args.constructor.constructor; |
|||
return F('return typeof process.platform')(); |
|||
}`);
|
|||
const out = await exec.executeScript(script, ['{}', '{}', 'X']); |
|||
assert.equal(out, 'string', |
|||
'use_sandbox=false is documented as non-isolating; if this fails, the path was changed and docs/threat model must be updated'); |
|||
}); |
|||
|
|||
test('non-sandbox path passes string args through unchanged', async () => { |
|||
const exec = new JsExecutor(false); |
|||
const script = await exec.compileScript(`function(msg, metadata, msgType){
|
|||
return { msgIsString: typeof msg === 'string', count: args.length, first: args[0] }; |
|||
}`);
|
|||
const out = await exec.executeScript(script, ['hello', '{}', 'X']); |
|||
assert.equal(out.msgIsString, true); |
|||
assert.equal(out.count, 3); |
|||
assert.equal(out.first, 'hello'); |
|||
}); |
|||
|
|||
}); // describe('js-executor')
|
|||
Loading…
Reference in new issue