or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

assertions.mdcompartments.mdenvironment-hardening.mdindex.mdmodules.mdtools.md

modules.mddocs/

0

# Module System

1

2

Advanced module loading system with hooks for custom resolution, transformation, and cross-compartment module sharing. The SES module system enables secure module loading with fine-grained control over how modules are resolved, loaded, and shared between compartments.

3

4

## Capabilities

5

6

### Module Loading Hooks

7

8

Hook functions that customize how modules are resolved and loaded within compartments.

9

10

```javascript { .api }

11

/**

12

* Resolves import specifiers to full module specifiers

13

* @param importSpecifier - The specifier used in import statement

14

* @param referrerSpecifier - The specifier of the importing module

15

* @returns Full module specifier for loading

16

*/

17

type ResolveHook = (importSpecifier: string, referrerSpecifier: string) => string;

18

19

/**

20

* Asynchronously loads modules by specifier

21

* @param moduleSpecifier - Full module specifier to load

22

* @returns Promise resolving to module descriptor

23

*/

24

type ImportHook = (moduleSpecifier: string) => Promise<ModuleDescriptor>;

25

26

/**

27

* Synchronously loads modules by specifier

28

* @param moduleSpecifier - Full module specifier to load

29

* @returns Module descriptor or undefined if not found

30

*/

31

type ImportNowHook = (moduleSpecifier: string) => ModuleDescriptor | undefined;

32

33

/**

34

* Dynamically maps module specifiers to descriptors

35

* @param moduleSpecifier - Module specifier to lookup

36

* @returns Module descriptor or undefined if not found

37

*/

38

type ModuleMapHook = (moduleSpecifier: string) => ModuleDescriptor | undefined;

39

40

/**

41

* Customizes import.meta objects for modules

42

* @param moduleSpecifier - The module's specifier

43

* @param importMeta - The import.meta object to customize

44

*/

45

type ImportMetaHook = (moduleSpecifier: string, importMeta: ImportMeta) => void;

46

```

47

48

**Usage Examples:**

49

50

```javascript

51

import 'ses';

52

53

lockdown();

54

55

// Custom module resolution

56

const resolveHook = (importSpecifier, referrerSpecifier) => {

57

if (importSpecifier.startsWith('./')) {

58

// Resolve relative imports

59

const referrerPath = referrerSpecifier.split('/').slice(0, -1);

60

return [...referrerPath, importSpecifier.slice(2)].join('/');

61

}

62

if (importSpecifier.startsWith('virtual:')) {

63

// Handle virtual modules

64

return importSpecifier;

65

}

66

// Default resolution

67

return importSpecifier;

68

};

69

70

// Async module loading

71

const importHook = async (moduleSpecifier) => {

72

if (moduleSpecifier.startsWith('virtual:math')) {

73

return {

74

source: `

75

export const add = (a, b) => a + b;

76

export const multiply = (a, b) => a * b;

77

export const PI = 3.14159;

78

`

79

};

80

}

81

if (moduleSpecifier.startsWith('http://')) {

82

const response = await fetch(moduleSpecifier);

83

const source = await response.text();

84

return { source };

85

}

86

throw new Error(`Cannot load module: ${moduleSpecifier}`);

87

};

88

89

// Sync module loading

90

const importNowHook = (moduleSpecifier) => {

91

if (moduleSpecifier === 'builtin:console') {

92

return {

93

namespace: { log: console.log, error: console.error }

94

};

95

}

96

// Return undefined for modules that can't be loaded synchronously

97

};

98

99

// Dynamic module mapping

100

const moduleMapHook = (moduleSpecifier) => {

101

const moduleRegistry = {

102

'app/config': {

103

namespace: { apiUrl: 'https://api.example.com', debug: true }

104

},

105

'app/utils': {

106

source: './utils/index.js',

107

compartment: utilsCompartment

108

}

109

};

110

return moduleRegistry[moduleSpecifier];

111

};

112

113

// Create compartment with hooks

114

const compartment = new Compartment({

115

resolveHook,

116

importHook,

117

importNowHook,

118

moduleMapHook,

119

__options__: true

120

});

121

```

122

123

### Module Descriptors

124

125

Various descriptor formats for specifying how modules should be loaded and initialized.

126

127

```javascript { .api }

128

/**

129

* Descriptor using source code or compiled module

130

*/

131

interface SourceModuleDescriptor {

132

/** Module source code or compiled module source */

133

source: string | ModuleSource;

134

/** Optional specifier for the module */

135

specifier?: string;

136

/** Optional import.meta properties */

137

importMeta?: any;

138

/** Optional compartment to load/initialize in */

139

compartment?: Compartment;

140

}

141

142

/**

143

* Descriptor using pre-existing namespace

144

*/

145

interface NamespaceModuleDescriptor {

146

/** Namespace object or specifier in another compartment */

147

namespace: string | ModuleExportsNamespace;

148

/** Optional compartment containing the namespace */

149

compartment?: Compartment;

150

}

151

152

/**

153

* Legacy descriptor format (being deprecated)

154

*/

155

interface RecordModuleDescriptor {

156

/** Module specifier */

157

specifier: string;

158

/** Optional compiled module record */

159

record?: ModuleSource;

160

/** Optional import.meta properties */

161

importMeta?: any;

162

/** Compartment to use (defaults to self) */

163

compartment?: Compartment;

164

}

165

166

/**

167

* Union type of all module descriptor formats

168

*/

169

type ModuleDescriptor =

170

| SourceModuleDescriptor

171

| NamespaceModuleDescriptor

172

| RecordModuleDescriptor

173

| ModuleExportsNamespace

174

| VirtualModuleSource

175

| PrecompiledModuleSource

176

| string;

177

```

178

179

**Usage Examples:**

180

181

```javascript

182

import 'ses';

183

184

lockdown();

185

186

const compartment = new Compartment({

187

modules: {

188

// Source descriptor with string source

189

'math/basic': {

190

source: `

191

export const add = (a, b) => a + b;

192

export const subtract = (a, b) => a - b;

193

`

194

},

195

196

// Namespace descriptor with direct object

197

'config/app': {

198

namespace: {

199

apiEndpoint: 'https://api.example.com',

200

version: '1.0.0'

201

}

202

},

203

204

// Cross-compartment namespace reference

205

'utils/shared': {

206

namespace: 'utils/index',

207

compartment: sharedUtilsCompartment

208

},

209

210

// With custom import.meta

211

'app/main': {

212

source: `

213

console.log('Module URL:', import.meta.url);

214

export const init = () => 'initialized';

215

`,

216

importMeta: {

217

url: 'file:///app/main.js',

218

env: 'production'

219

}

220

}

221

},

222

__options__: true

223

});

224

```

225

226

### Module Source Types

227

228

Different types of module source representations for advanced use cases.

229

230

```javascript { .api }

231

/**

232

* Pre-compiled module source with analysis metadata

233

*/

234

interface PrecompiledModuleSource {

235

/** Array of import specifiers */

236

imports: Array<string>;

237

/** Array of export names */

238

exports: Array<string>;

239

/** Array of re-export specifiers */

240

reexports: Array<string>;

241

/** Compiled module program as string */

242

__syncModuleProgram__: string;

243

/** Map of live (mutable) export bindings */

244

__liveExportMap__: __LiveExportMap__;

245

/** Map of fixed (immutable) export bindings */

246

__fixedExportMap__: __FixedExportMap__;

247

/** Map of re-export bindings */

248

__reexportMap__: __ReexportMap__;

249

}

250

251

/**

252

* Virtual module source with custom execution logic

253

*/

254

interface VirtualModuleSource {

255

/** Array of import specifiers this module needs */

256

imports: Array<string>;

257

/** Array of names this module exports */

258

exports: Array<string>;

259

260

/**

261

* Custom execution function for the module

262

* @param exportsTarget - Object to populate with exports

263

* @param compartment - The compartment executing the module

264

* @param resolvedImports - Map of import names to resolved specifiers

265

*/

266

execute(

267

exportsTarget: Record<string, any>,

268

compartment: Compartment,

269

resolvedImports: Record<string, string>

270

): void;

271

}

272

273

/** Union type of module source formats */

274

type ModuleSource = PrecompiledModuleSource | VirtualModuleSource;

275

```

276

277

**Usage Examples:**

278

279

```javascript

280

import 'ses';

281

282

lockdown();

283

284

// Virtual module source example

285

const createDatabaseModule = (connectionString) => ({

286

imports: [],

287

exports: ['connect', 'query', 'disconnect'],

288

execute(exportsTarget, compartment, resolvedImports) {

289

let connection = null;

290

291

exportsTarget.connect = async () => {

292

connection = await createConnection(connectionString);

293

return connection;

294

};

295

296

exportsTarget.query = async (sql, params) => {

297

if (!connection) throw new Error('Not connected');

298

return await connection.query(sql, params);

299

};

300

301

exportsTarget.disconnect = async () => {

302

if (connection) {

303

await connection.close();

304

connection = null;

305

}

306

};

307

}

308

});

309

310

// Use virtual module

311

const compartment = new Compartment({

312

modules: {

313

'db/connection': createDatabaseModule('postgresql://localhost/mydb')

314

},

315

__options__: true

316

});

317

318

const db = compartment.importNow('db/connection');

319

await db.connect();

320

const results = await db.query('SELECT * FROM users WHERE active = ?', [true]);

321

```

322

323

## Advanced Module Patterns

324

325

### Module Federation

326

327

Sharing modules across multiple compartments:

328

329

```javascript

330

import 'ses';

331

332

lockdown();

333

334

// Shared library compartment

335

const libraryCompartment = new Compartment({

336

modules: {

337

'lodash': {

338

source: `

339

export const map = (array, fn) => array.map(fn);

340

export const filter = (array, fn) => array.filter(fn);

341

export const reduce = (array, fn, initial) => array.reduce(fn, initial);

342

`

343

},

344

'validator': {

345

source: `

346

export const isEmail = (str) => /\\S+@\\S+\\.\\S+/.test(str);

347

export const isURL = (str) => /^https?:\\/\\//.test(str);

348

`

349

}

350

},

351

__options__: true

352

});

353

354

// Application compartments that use shared libraries

355

const createAppCompartment = (name) => new Compartment({

356

name,

357

modules: {

358

// Reference modules from library compartment

359

'lodash': {

360

namespace: 'lodash',

361

compartment: libraryCompartment

362

},

363

'validator': {

364

namespace: 'validator',

365

compartment: libraryCompartment

366

}

367

},

368

__options__: true

369

});

370

371

const userAppCompartment = createAppCompartment('user-app');

372

const adminAppCompartment = createAppCompartment('admin-app');

373

374

// Both apps can use the same shared modules

375

const userLodash = userAppCompartment.importNow('lodash');

376

const adminValidator = adminAppCompartment.importNow('validator');

377

console.log(userLodash === adminValidator); // false (different namespaces)

378

```

379

380

### Dynamic Module Loading

381

382

Loading modules based on runtime conditions:

383

384

```javascript

385

import 'ses';

386

387

lockdown();

388

389

const createPluginSystem = () => {

390

const plugins = new Map();

391

392

return new Compartment({

393

moduleMapHook: (specifier) => {

394

if (specifier.startsWith('plugin:')) {

395

const pluginName = specifier.slice(7);

396

return plugins.get(pluginName);

397

}

398

},

399

400

importHook: async (specifier) => {

401

if (specifier.startsWith('dynamic:')) {

402

const moduleName = specifier.slice(8);

403

404

// Load module based on environment

405

if (process.env.NODE_ENV === 'development') {

406

return {

407

source: `export default { name: '${moduleName}', env: 'dev' };`

408

};

409

} else {

410

return {

411

source: `export default { name: '${moduleName}', env: 'prod' };`

412

};

413

}

414

}

415

416

throw new Error(`Unknown module: ${specifier}`);

417

},

418

419

globals: {

420

registerPlugin: harden((name, descriptor) => {

421

plugins.set(name, descriptor);

422

}),

423

console: harden(console)

424

},

425

426

__options__: true

427

});

428

};

429

430

const pluginSystem = createPluginSystem();

431

432

// Register a plugin

433

pluginSystem.globalThis.registerPlugin('auth', {

434

source: `

435

export const authenticate = (token) => token === 'valid';

436

export const authorize = (user, resource) => user.role === 'admin';

437

`

438

});

439

440

// Use the plugin

441

const auth = pluginSystem.importNow('plugin:auth');

442

console.log(auth.authenticate('valid')); // true

443

444

// Load dynamic module

445

const dynModule = await pluginSystem.import('dynamic:feature-a');

446

console.log(dynModule.default); // { name: 'feature-a', env: 'dev' or 'prod' }

447

```

448

449

### Module Transformation Pipeline

450

451

Transforming modules during loading:

452

453

```javascript

454

import 'ses';

455

456

lockdown();

457

458

const createTransformingCompartment = () => {

459

const transforms = [

460

// Add logging to all functions

461

(source) => {

462

return source.replace(

463

/export\s+(?:const|function)\s+(\w+)/g,

464

(match, name) => {

465

return match + `\nconsole.log('Calling ${name}');`;

466

}

467

);

468

},

469

470

// Replace development assertions

471

(source) => {

472

if (process.env.NODE_ENV === 'production') {

473

return source.replace(/assert\([^)]+\);?/g, '');

474

}

475

return source;

476

}

477

];

478

479

return new Compartment({

480

importHook: async (specifier) => {

481

let source = await fetchModuleSource(specifier);

482

483

// Apply transformations

484

for (const transform of transforms) {

485

source = transform(source);

486

}

487

488

return { source };

489

},

490

491

globals: { console: harden(console) },

492

__options__: true

493

});

494

};

495

496

const fetchModuleSource = async (specifier) => {

497

// Mock module source

498

if (specifier === 'math-utils') {

499

return `

500

export const add = (a, b) => {

501

assert(typeof a === 'number');

502

assert(typeof b === 'number');

503

return a + b;

504

};

505

506

export const multiply = (a, b) => {

507

assert(typeof a === 'number');

508

assert(typeof b === 'number');

509

return a * b;

510

};

511

`;

512

}

513

throw new Error(`Module not found: ${specifier}`);

514

};

515

516

const compartment = createTransformingCompartment();

517

const mathUtils = await compartment.import('math-utils');

518

console.log(mathUtils.namespace.add(2, 3)); // Logs: "Calling add" then returns 5

519

```

520

521

### Module Sandboxing

522

523

Creating isolated environments for different types of modules:

524

525

```javascript

526

import 'ses';

527

528

lockdown();

529

530

const createSandboxedModule = (source, permissions = {}) => {

531

const sandbox = new Compartment({

532

globals: {

533

// Only provide explicitly allowed capabilities

534

...(permissions.console && { console: harden(console) }),

535

...(permissions.setTimeout && { setTimeout: harden(setTimeout) }),

536

...(permissions.fetch && { fetch: harden(fetch) }),

537

538

// Custom API surface

539

...(permissions.storage && {

540

storage: harden({

541

get: (key) => localStorage.getItem(`sandbox:${key}`),

542

set: (key, value) => localStorage.setItem(`sandbox:${key}`, value)

543

})

544

})

545

},

546

__options__: true

547

});

548

549

return sandbox.evaluate(`

550

${source}

551

// Return the module's exports

552

(typeof exports !== 'undefined' ? exports :

553

typeof module !== 'undefined' && module.exports ? module.exports :

554

{})

555

`);

556

};

557

558

// Load user-provided plugin with minimal permissions

559

const userPlugin = createSandboxedModule(`

560

exports.processData = (data) => {

561

console.log('Processing:', data);

562

return data.map(x => x * 2);

563

};

564

`, {

565

console: true // Only allow console access

566

});

567

568

// Load trusted module with more permissions

569

const trustedModule = createSandboxedModule(`

570

exports.saveData = async (data) => {

571

storage.set('lastData', JSON.stringify(data));

572

const response = await fetch('/api/save', {

573

method: 'POST',

574

body: JSON.stringify(data)

575

});

576

return response.json();

577

};

578

`, {

579

console: true,

580

storage: true,

581

fetch: true

582

});

583

584

// Use the sandboxed modules

585

const processedData = userPlugin.processData([1, 2, 3]); // [2, 4, 6]

586

await trustedModule.saveData(processedData);

587

```