0
# Security and Debugging
1
2
Comprehensive security framework and debugging capabilities for production JavaScript execution environments, including access control, sandboxing, error handling, and runtime introspection.
3
4
## Capabilities
5
6
### Security Framework
7
8
#### ClassShutter
9
10
Controls which Java classes are accessible from JavaScript code, providing fine-grained access control.
11
12
```java { .api }
13
/**
14
* Interface for controlling Java class access from JavaScript
15
* Prevents access to sensitive or dangerous Java classes
16
*/
17
public interface ClassShutter {
18
/**
19
* Determines if a Java class should be accessible from JavaScript
20
* @param fullClassName Fully qualified Java class name
21
* @return true if class should be accessible, false otherwise
22
*/
23
boolean visibleToScripts(String fullClassName);
24
}
25
```
26
27
**Usage Examples:**
28
29
```java
30
// Restrictive ClassShutter implementation
31
ClassShutter restrictiveShutter = new ClassShutter() {
32
@Override
33
public boolean visibleToScripts(String fullClassName) {
34
// Allow only safe classes
35
return fullClassName.startsWith("java.lang.String") ||
36
fullClassName.startsWith("java.lang.Math") ||
37
fullClassName.startsWith("java.util.Date");
38
}
39
};
40
41
// Set ClassShutter on Context
42
cx.setClassShutter(restrictiveShutter);
43
44
// Now JavaScript can only access allowed classes
45
try {
46
cx.evaluateString(scope, "java.lang.System.exit(0);", "test", 1, null);
47
// This will fail - System class not accessible
48
} catch (EvaluatorException e) {
49
System.out.println("Access denied: " + e.getMessage());
50
}
51
52
// Whitelist-based ClassShutter
53
Set<String> allowedClasses = Set.of(
54
"java.lang.String",
55
"java.lang.Math",
56
"java.util.ArrayList",
57
"java.util.HashMap"
58
);
59
60
ClassShutter whitelistShutter = allowedClasses::contains;
61
cx.setClassShutter(whitelistShutter);
62
```
63
64
#### SecurityController
65
66
Provides comprehensive security control over script execution with customizable policies.
67
68
```java { .api }
69
/**
70
* Controls script execution security and access permissions
71
* Provides integration with Java security manager
72
*/
73
public abstract class SecurityController {
74
/**
75
* Gets the global SecurityController instance
76
* @return Global SecurityController or null if none set
77
*/
78
public static SecurityController global();
79
80
/**
81
* Checks if global SecurityController is installed
82
* @return true if global controller exists
83
*/
84
public static boolean hasGlobal();
85
86
/**
87
* Sets the global SecurityController instance
88
* @param controller SecurityController to install globally
89
*/
90
public static void initGlobal(SecurityController controller);
91
92
/**
93
* Creates call context for secure execution
94
* @param cx Current Context
95
* @param callable Callable object
96
* @param scope Execution scope
97
* @param thisObj 'this' object
98
* @param args Call arguments
99
* @return Execution result
100
*/
101
public abstract Object callWithDomain(Object securityDomain, Context cx, Callable callable,
102
Scriptable scope, Scriptable thisObj, Object[] args);
103
104
/**
105
* Gets current security domain
106
* @param cx Current Context
107
* @return Current security domain object
108
*/
109
public abstract Object getDynamicSecurityDomain(Object securityDomain);
110
111
/**
112
* Gets class loader for generated classes
113
* @param cx Current Context
114
* @param securityDomain Security domain
115
* @return ClassLoader for generated classes
116
*/
117
public abstract ClassLoader createClassLoader(ClassLoader parent, Object securityDomain);
118
}
119
120
/**
121
* SecurityController implementation using Java security policy
122
* Integrates with Java AccessController and Policy system
123
*/
124
public class PolicySecurityController extends SecurityController {
125
/**
126
* Creates PolicySecurityController instance
127
*/
128
public PolicySecurityController();
129
}
130
```
131
132
**Usage Examples:**
133
134
```java
135
// Install security controller
136
PolicySecurityController securityController = new PolicySecurityController();
137
SecurityController.initGlobal(securityController);
138
139
// Execute scripts with security domain
140
Object securityDomain = "trusted-domain";
141
Context cx = Context.enter();
142
try {
143
cx.setSecurityController(securityController);
144
// Scripts compiled with this domain will have restricted permissions
145
Script script = cx.compileString("java.lang.System.getProperty('user.home')",
146
"test", 1, securityDomain);
147
Object result = script.exec(cx, scope);
148
} finally {
149
Context.exit();
150
}
151
```
152
153
#### Safe Standard Objects
154
155
Initialize JavaScript environment without Java class access for sandboxed execution.
156
157
```java { .api }
158
/**
159
* Initialize safe JavaScript environment without Java access
160
* Prevents JavaScript code from accessing Java packages and classes
161
*/
162
public ScriptableObject initSafeStandardObjects();
163
164
/**
165
* Initialize safe environment with custom scope object
166
* @param scope Scope object to initialize
167
* @return Initialized safe scope
168
*/
169
public Scriptable initSafeStandardObjects(ScriptableObject scope);
170
```
171
172
**Usage Examples:**
173
174
```java
175
// Create safe JavaScript environment
176
Context cx = Context.enter();
177
try {
178
// Safe scope - no Java access
179
Scriptable safeScope = cx.initSafeStandardObjects();
180
181
// This will work - standard JavaScript
182
Object result1 = cx.evaluateString(safeScope,
183
"Math.max(1, 2, 3)", "safe", 1, null);
184
185
// This will fail - no Java access
186
try {
187
cx.evaluateString(safeScope,
188
"java.lang.System.getProperty('user.name')", "unsafe", 1, null);
189
} catch (EvaluatorException e) {
190
System.out.println("Java access blocked: " + e.getMessage());
191
}
192
} finally {
193
Context.exit();
194
}
195
196
// Custom safe scope with additional restrictions
197
ScriptableObject customScope = cx.initSafeStandardObjects();
198
// Remove potentially dangerous global functions
199
customScope.delete("eval"); // Disable eval
200
customScope.delete("Function"); // Disable Function constructor
201
```
202
203
### Error Handling and Reporting
204
205
#### ErrorReporter Interface
206
207
Handles compilation and runtime errors with customizable error processing.
208
209
```java { .api }
210
/**
211
* Interface for reporting JavaScript errors and warnings
212
* Allows custom error handling and logging
213
*/
214
public interface ErrorReporter {
215
/**
216
* Reports a warning during compilation or execution
217
* @param message Warning message
218
* @param sourceName Source file name
219
* @param line Line number where warning occurred
220
* @param lineSource Source code line
221
* @param lineOffset Column offset in line
222
*/
223
void warning(String message, String sourceName, int line, String lineSource, int lineOffset);
224
225
/**
226
* Reports an error during compilation or execution
227
* @param message Error message
228
* @param sourceName Source file name
229
* @param line Line number where error occurred
230
* @param lineSource Source code line
231
* @param lineOffset Column offset in line
232
*/
233
void error(String message, String sourceName, int line, String lineSource, int lineOffset);
234
235
/**
236
* Reports a runtime error and returns exception to throw
237
* @param message Error message
238
* @param sourceName Source file name
239
* @param line Line number where error occurred
240
* @param lineSource Source code line
241
* @param lineOffset Column offset in line
242
* @return EvaluatorException to throw
243
*/
244
EvaluatorException runtimeError(String message, String sourceName, int line, String lineSource, int lineOffset);
245
}
246
247
/**
248
* Default ErrorReporter implementation
249
* Prints errors to System.err and creates standard exceptions
250
*/
251
public class DefaultErrorReporter implements ErrorReporter {
252
/**
253
* Gets singleton instance of DefaultErrorReporter
254
* @return Default ErrorReporter instance
255
*/
256
public static ErrorReporter instance();
257
}
258
```
259
260
**Usage Examples:**
261
262
```java
263
// Custom error reporter with logging
264
ErrorReporter loggingReporter = new ErrorReporter() {
265
private final Logger logger = Logger.getLogger("JavaScript");
266
267
@Override
268
public void warning(String message, String sourceName, int line,
269
String lineSource, int lineOffset) {
270
logger.warning(String.format("JS Warning at %s:%d - %s",
271
sourceName, line, message));
272
}
273
274
@Override
275
public void error(String message, String sourceName, int line,
276
String lineSource, int lineOffset) {
277
logger.severe(String.format("JS Error at %s:%d - %s",
278
sourceName, line, message));
279
}
280
281
@Override
282
public EvaluatorException runtimeError(String message, String sourceName, int line,
283
String lineSource, int lineOffset) {
284
logger.severe(String.format("JS Runtime Error at %s:%d - %s",
285
sourceName, line, message));
286
return new EvaluatorException(message, sourceName, line, lineSource, lineOffset);
287
}
288
};
289
290
// Set custom error reporter
291
cx.setErrorReporter(loggingReporter);
292
293
// Errors will now be logged
294
try {
295
cx.evaluateString(scope, "invalidSyntax(((", "test.js", 1, null);
296
} catch (EvaluatorException e) {
297
// Error was logged by custom reporter
298
}
299
```
300
301
#### Exception Hierarchy
302
303
Comprehensive exception hierarchy for different types of JavaScript errors.
304
305
```java { .api }
306
/**
307
* Base class for all Rhino-specific exceptions
308
* Provides source location information for debugging
309
*/
310
public abstract class RhinoException extends RuntimeException {
311
/**
312
* Gets source file name where error occurred
313
* @return Source file name or null
314
*/
315
public String sourceName();
316
317
/**
318
* Gets line number where error occurred
319
* @return Line number or -1 if unknown
320
*/
321
public int lineNumber();
322
323
/**
324
* Gets source code line where error occurred
325
* @return Source line or null
326
*/
327
public String lineSource();
328
329
/**
330
* Gets column number where error occurred
331
* @return Column number or -1 if unknown
332
*/
333
public int columnNumber();
334
335
/**
336
* Gets JavaScript stack trace
337
* @return Stack trace string
338
*/
339
public String getScriptStackTrace();
340
}
341
342
/**
343
* Exception for compilation and evaluation errors
344
* Thrown when JavaScript syntax is invalid or evaluation fails
345
*/
346
public class EvaluatorException extends RhinoException {
347
/**
348
* Creates EvaluatorException with location info
349
* @param detail Error detail message
350
* @param sourceName Source file name
351
* @param lineNumber Line number
352
* @param lineSource Source line
353
* @param columnNumber Column number
354
*/
355
public EvaluatorException(String detail, String sourceName, int lineNumber,
356
String lineSource, int columnNumber);
357
}
358
359
/**
360
* Exception for ECMA-standard JavaScript runtime errors
361
* Represents Error objects thrown by JavaScript code
362
*/
363
public class EcmaError extends RhinoException {
364
/**
365
* Creates EcmaError from JavaScript Error object
366
* @param nativeError JavaScript Error object
367
* @param sourceName Source file name
368
* @param lineNumber Line number
369
* @param lineSource Source line
370
* @param columnNumber Column number
371
*/
372
public EcmaError(Scriptable nativeError, String sourceName, int lineNumber,
373
String lineSource, int columnNumber);
374
375
/**
376
* Gets the JavaScript Error object
377
* @return Native Error object
378
*/
379
public Scriptable getErrorObject();
380
381
/**
382
* Gets error name (TypeError, ReferenceError, etc.)
383
* @return Error type name
384
*/
385
public String getName();
386
}
387
388
/**
389
* Exception for values thrown from JavaScript throw statements
390
* Wraps any JavaScript value that was thrown
391
*/
392
public class JavaScriptException extends RhinoException {
393
/**
394
* Creates JavaScriptException wrapping thrown value
395
* @param value JavaScript value that was thrown
396
* @param sourceName Source file name
397
* @param lineNumber Line number
398
*/
399
public JavaScriptException(Object value, String sourceName, int lineNumber);
400
401
/**
402
* Gets the thrown JavaScript value
403
* @return Original thrown value
404
*/
405
public Object getValue();
406
}
407
408
/**
409
* Exception wrapping Java exceptions thrown from JavaScript
410
* Occurs when Java methods called from JavaScript throw exceptions
411
*/
412
public class WrappedException extends EvaluatorException {
413
/**
414
* Creates WrappedException wrapping Java exception
415
* @param exception Original Java exception
416
*/
417
public WrappedException(Throwable exception);
418
419
/**
420
* Gets the wrapped Java exception
421
* @return Original Java exception
422
*/
423
public Throwable getWrappedException();
424
}
425
```
426
427
**Usage Examples:**
428
429
```java
430
// Comprehensive error handling
431
try {
432
Object result = cx.evaluateString(scope, jsCode, "test.js", 1, null);
433
} catch (EvaluatorException ee) {
434
// Compilation or evaluation error
435
System.err.println("Evaluation error at " + ee.sourceName() + ":" + ee.lineNumber());
436
System.err.println("Message: " + ee.getMessage());
437
if (ee.lineSource() != null) {
438
System.err.println("Source: " + ee.lineSource());
439
}
440
} catch (EcmaError ee) {
441
// JavaScript Error object thrown
442
System.err.println("JavaScript " + ee.getName() + ": " + ee.getMessage());
443
System.err.println("At " + ee.sourceName() + ":" + ee.lineNumber());
444
} catch (JavaScriptException jse) {
445
// JavaScript throw statement
446
Object thrownValue = jse.getValue();
447
System.err.println("JavaScript threw: " + Context.toString(thrownValue));
448
System.err.println("At " + jse.sourceName() + ":" + jse.lineNumber());
449
} catch (WrappedException we) {
450
// Java exception from JavaScript-called Java method
451
Throwable original = we.getWrappedException();
452
System.err.println("Java exception in JavaScript: " + original.getClass().getName());
453
System.err.println("Message: " + original.getMessage());
454
original.printStackTrace();
455
}
456
```
457
458
### Debugging Support
459
460
#### Debugger Interface
461
462
Core debugging interface for runtime inspection and control.
463
464
```java { .api }
465
/**
466
* Main debugger interface for JavaScript execution debugging
467
* Provides hooks for compilation and execution events
468
*/
469
public interface Debugger {
470
/**
471
* Called when a script or function is compiled
472
* @param cx Current Context
473
* @param fnOrScript Compiled script or function
474
* @param source JavaScript source code
475
*/
476
void handleCompilationDone(Context cx, DebuggableScript fnOrScript, String source);
477
478
/**
479
* Gets debug frame for script execution
480
* @param cx Current Context
481
* @param fnOrScript Script or function being executed
482
* @return DebugFrame for tracking execution
483
*/
484
DebugFrame getFrame(Context cx, DebuggableScript fnOrScript);
485
}
486
487
/**
488
* Debug frame for tracking script execution
489
* Provides hooks for execution events and variable access
490
*/
491
public interface DebugFrame {
492
/**
493
* Called when execution enters function/script
494
* @param cx Current Context
495
* @param activation Activation object (local variables)
496
* @param thisObj 'this' object for execution
497
* @param args Function arguments
498
*/
499
void onEnter(Context cx, Scriptable activation, Scriptable thisObj, Object[] args);
500
501
/**
502
* Called when execution reaches new source line
503
* @param cx Current Context
504
* @param lineNumber Current line number
505
*/
506
void onLineChange(Context cx, int lineNumber);
507
508
/**
509
* Called when exception is thrown
510
* @param cx Current Context
511
* @param ex Exception that was thrown
512
*/
513
void onExceptionThrown(Context cx, Throwable ex);
514
515
/**
516
* Called when execution exits function/script
517
* @param cx Current Context
518
* @param byThrow true if exiting due to exception
519
* @param resultOrException Return value or exception
520
*/
521
void onExit(Context cx, boolean byThrow, Object resultOrException);
522
}
523
524
/**
525
* Provides debug information about compiled scripts
526
* Used by debugger to understand script structure
527
*/
528
public interface DebuggableScript {
529
/**
530
* Checks if this is top-level script (not function)
531
* @return true if top-level script
532
*/
533
boolean isTopLevel();
534
535
/**
536
* Checks if this is a function
537
* @return true if function
538
*/
539
boolean isFunction();
540
541
/**
542
* Gets function name (null for anonymous)
543
* @return Function name or null
544
*/
545
String getFunctionName();
546
547
/**
548
* Gets source file name
549
* @return Source file name
550
*/
551
String getSourceName();
552
553
/**
554
* Gets array of line numbers with executable code
555
* @return Array of line numbers
556
*/
557
int[] getLineNumbers();
558
559
/**
560
* Gets number of nested functions
561
* @return Function count
562
*/
563
int getFunctionCount();
564
565
/**
566
* Gets nested function by index
567
* @param index Function index
568
* @return Nested DebuggableScript
569
*/
570
DebuggableScript getFunction(int index);
571
572
/**
573
* Gets parameter names for function
574
* @return Array of parameter names
575
*/
576
String[] getParamNames();
577
}
578
```
579
580
**Usage Examples:**
581
582
```java
583
// Simple debugger implementation
584
Debugger simpleDebugger = new Debugger() {
585
@Override
586
public void handleCompilationDone(Context cx, DebuggableScript fnOrScript, String source) {
587
System.out.println("Compiled: " + fnOrScript.getSourceName() +
588
(fnOrScript.isFunction() ? " (function)" : " (script)"));
589
}
590
591
@Override
592
public DebugFrame getFrame(Context cx, DebuggableScript fnOrScript) {
593
return new DebugFrame() {
594
@Override
595
public void onEnter(Context cx, Scriptable activation,
596
Scriptable thisObj, Object[] args) {
597
System.out.println("Entering: " + fnOrScript.getFunctionName());
598
}
599
600
@Override
601
public void onLineChange(Context cx, int lineNumber) {
602
System.out.println("Line: " + lineNumber);
603
}
604
605
@Override
606
public void onExceptionThrown(Context cx, Throwable ex) {
607
System.out.println("Exception: " + ex.getMessage());
608
}
609
610
@Override
611
public void onExit(Context cx, boolean byThrow, Object resultOrException) {
612
System.out.println("Exit" + (byThrow ? " (exception)" : " (normal)"));
613
}
614
};
615
}
616
};
617
618
// Enable debugging
619
cx.setDebugger(simpleDebugger, null);
620
cx.setGeneratingDebug(true);
621
622
// Execute with debugging
623
cx.evaluateString(scope, """
624
function test(x) {
625
var y = x * 2;
626
return y + 1;
627
}
628
test(5);
629
""", "debug-test.js", 1, null);
630
```
631
632
#### Stack Introspection
633
634
Runtime stack inspection for debugging and error reporting.
635
636
```java { .api }
637
/**
638
* Represents an element in the JavaScript call stack
639
* Provides file, function, and line information
640
*/
641
public class ScriptStackElement {
642
/**
643
* Source file name
644
*/
645
public final String fileName;
646
647
/**
648
* Function name (null for top-level)
649
*/
650
public final String functionName;
651
652
/**
653
* Line number in source
654
*/
655
public final int lineNumber;
656
657
/**
658
* Creates stack element
659
* @param fileName Source file name
660
* @param functionName Function name
661
* @param lineNumber Line number
662
*/
663
public ScriptStackElement(String fileName, String functionName, int lineNumber);
664
665
/**
666
* String representation for debugging
667
* @return Formatted stack element string
668
*/
669
public String toString();
670
}
671
```
672
673
**Usage Examples:**
674
675
```java
676
// Get current JavaScript stack trace
677
try {
678
cx.evaluateString(scope, """
679
function a() { b(); }
680
function b() { c(); }
681
function c() { throw new Error('test'); }
682
a();
683
""", "stack-test.js", 1, null);
684
} catch (JavaScriptException jse) {
685
// Get stack trace from exception
686
String stackTrace = jse.getScriptStackTrace();
687
System.out.println("JavaScript stack trace:");
688
System.out.println(stackTrace);
689
690
// Parse stack elements if needed
691
String[] lines = stackTrace.split("\n");
692
for (String line : lines) {
693
System.out.println("Stack frame: " + line);
694
}
695
}
696
```
697
698
### Advanced Security Features
699
700
#### Context Feature Controls
701
702
Fine-grained control over JavaScript language features for security.
703
704
```java { .api }
705
// Feature constants for security control
706
public static final int FEATURE_STRICT_MODE = 11;
707
public static final int FEATURE_ENHANCED_JAVA_ACCESS = 13;
708
public static final int FEATURE_E4X = 6; // E4X XML literals
709
public static final int FEATURE_DYNAMIC_SCOPE = 7;
710
public static final int FEATURE_STRICT_VARS = 8;
711
public static final int FEATURE_STRICT_EVAL = 9;
712
public static final int FEATURE_WARNING_AS_ERROR = 12;
713
public static final int FEATURE_THREAD_SAFE_OBJECTS = 17;
714
715
/**
716
* Checks if feature is enabled in current Context
717
* @param featureIndex Feature constant
718
* @return true if feature is enabled
719
*/
720
public boolean hasFeature(int featureIndex);
721
```
722
723
**Usage Examples:**
724
725
```java
726
// Disable potentially dangerous features
727
Context cx = Context.enter();
728
try {
729
// Enable strict mode
730
cx.setLanguageVersion(Context.VERSION_ES6);
731
732
// Disable enhanced Java access
733
if (cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS)) {
734
// Custom ContextFactory can override feature settings
735
}
736
737
// Enable strict variable declarations
738
// (requires custom ContextFactory to override hasFeature)
739
740
} finally {
741
Context.exit();
742
}
743
744
// Custom ContextFactory for security
745
ContextFactory secureFactory = new ContextFactory() {
746
@Override
747
protected boolean hasFeature(Context cx, int featureIndex) {
748
switch (featureIndex) {
749
case Context.FEATURE_ENHANCED_JAVA_ACCESS:
750
return false; // Disable enhanced Java access
751
case Context.FEATURE_STRICT_MODE:
752
return true; // Force strict mode
753
case Context.FEATURE_E4X:
754
return false; // Disable E4X XML literals
755
default:
756
return super.hasFeature(cx, featureIndex);
757
}
758
}
759
};
760
761
// Use secure factory
762
Object result = secureFactory.call(cx -> {
763
Scriptable scope = cx.initSafeStandardObjects();
764
return cx.evaluateString(scope, jsCode, "secure.js", 1, null);
765
});
766
```
767
768
#### Timeout and Resource Control
769
770
Protection against infinite loops and resource exhaustion.
771
772
```java
773
// Custom ContextFactory with timeout
774
ContextFactory timeoutFactory = new ContextFactory() {
775
@Override
776
protected void observeInstructionCount(Context cx, int instructionCount) {
777
// Check if execution should be interrupted
778
long currentTime = System.currentTimeMillis();
779
long startTime = (Long) cx.getThreadLocal("startTime");
780
if (currentTime - startTime > 5000) { // 5 second timeout
781
throw new Error("Script execution timeout");
782
}
783
}
784
785
@Override
786
protected Context makeContext() {
787
Context cx = super.makeContext();
788
cx.setInstructionObserverThreshold(10000); // Check every 10k instructions
789
cx.putThreadLocal("startTime", System.currentTimeMillis());
790
return cx;
791
}
792
};
793
794
// Execute with timeout protection
795
try {
796
timeoutFactory.call(cx -> {
797
Scriptable scope = cx.initStandardObjects();
798
return cx.evaluateString(scope, "while(true) {}", "infinite.js", 1, null);
799
});
800
} catch (Error e) {
801
System.out.println("Execution interrupted: " + e.getMessage());
802
}
803
```