JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 5844
  • Score
    100M100P100Q125101F
  • License Apache-2.0

A production-ready AST security guard for JavaScript - validate, protect, and enforce code safety with extensible rules

Package Exports

  • ast-guard
  • ast-guard/package.json

Readme

AST Guard

npm version License TypeScript

A production-ready, extensible AST validator for JavaScript with rule-based validation

AST Guard is a powerful static analysis tool for JavaScript code. It provides a flexible, rule-based architecture for validating Abstract Syntax Trees (AST) with comprehensive built-in rules and support for custom validation logic.

Bank-Grade Security

Hardened Against All Known Sandbox Escape Exploits

Metric Value
CVE Protection 100% (all known vm2, isolated-vm, node-vm exploits)
Security Tests 613 tests, 100% pass rate
Code Coverage 95%+
Defense Layers 4 (AST validation, transformation, proxy, isolation)

Protected Against:

  • vm2 CVEs: CVE-2023-29017, CVE-2023-30547, CVE-2023-32313, CVE-2023-37466
  • Constructor chain escapes: [].constructor.constructor('code')()
  • Prototype pollution: __proto__, Object.setPrototypeOf
  • Reflection API bypasses: Reflect.get, Reflect.construct
  • Global object access: window, globalThis, this

For detailed CVE analysis, see CVE-COVERAGE.md. For the full list of 100+ blocked attack vectors, see SECURITY-AUDIT.md.

Features

  • Production-Ready: Comprehensive error handling, type-safe APIs, and battle-tested rules
  • Extensible: Plugin architecture with custom rule support
  • Type-Safe: Full TypeScript support with strict typing
  • Zero Dependencies: Lightweight with only acorn and acorn-walk as dependencies
  • Comprehensive Rules: Built-in rules for common validation scenarios
  • Security-Focused: Rules to prevent dangerous code patterns
  • Performance: Fast validation with configurable limits
  • Detailed Reporting: Rich error messages with source locations

Installation

npm install ast-guard
# or
yarn add ast-guard
# or
pnpm add ast-guard

Quick Start

import { JSAstValidator, DisallowedIdentifierRule, NoEvalRule } from 'ast-guard';

// Create validator with built-in rules
const validator = new JSAstValidator([
  new DisallowedIdentifierRule({ disallowed: ['eval', 'Function'] }),
  new NoEvalRule(),
]);

// Validate code
const result = await validator.validate(`
  const x = 1;
  eval("alert('hello')");
`);

if (!result.valid) {
  console.log('Validation failed:');
  result.issues.forEach((issue) => {
    console.log(`- [${issue.severity}] ${issue.message} (${issue.code})`);
    if (issue.location) {
      console.log(`  at line ${issue.location.line}, column ${issue.location.column}`);
    }
  });
}

Presets

AST Guard provides pre-configured security presets for quick and easy setup. Choose from five security levels, from maximum lockdown to minimal restrictions.

Available Presets

Preset Security Level Description Use Case
STRICT Maximum Blocks all dangerous patterns, loops, and async Untrusted code execution
SECURE High Blocks most dangerous patterns, allows bounded loops Semi-trusted code with restrictions
STANDARD Medium Sensible defaults, blocks critical risks only Trusted code with safety checks
PERMISSIVE Low Minimal restrictions, only blocks eval Internal scripts

Quick Start with Presets

import { JSAstValidator, Presets, PresetLevel } from 'ast-guard';

// Option 1: Use the Presets object
const lockdownRules = Presets.strict();
const validator = new JSAstValidator(lockdownRules);

// Option 2: Use createPreset with level
import { createPreset } from 'ast-guard';
const securedRules = createPreset(PresetLevel.SECURE);
const validator = new JSAstValidator(securedRules);

// Option 3: Use individual preset function
import { createStandardPreset } from 'ast-guard';
const balancedRules = createStandardPreset();
const validator = new JSAstValidator(balancedRules);

Preset Details

STRICT Preset

Maximum security for untrusted code. Bank-grade protection.

Includes all security rules to block known sandbox escape exploits:

  • ✅ NoEvalRule - Blocks eval() and Function constructor
  • ✅ NoGlobalAccessRule - Blocks constructor chains, Reflect API, global object access
  • ✅ NoAsyncRule - Blocks async/await patterns
  • ✅ DisallowedIdentifierRule - Blocks dangerous identifiers
  • ✅ ForbiddenLoopRule - Blocks loops (or optionally allow bounded loops)
  • ✅ And more...
import { Presets } from 'ast-guard';

// Maximum lockdown
const rules = Presets.strict();

// With customization
const rules = Presets.strict({
  // Allow specific loops
  allowedLoops: { allowFor: true, allowForOf: true },

  // Require specific API calls
  requiredFunctions: ['callTool'],
  minFunctionCalls: 1,
  maxFunctionCalls: 10,

  // Validate function arguments
  functionArgumentRules: {
    callTool: {
      minArgs: 2,
      expectedTypes: ['string', 'object'],
    },
  },

  // Add custom blocked identifiers
  additionalDisallowedIdentifiers: ['window', 'document'],
});

Blocks:

  • All eval-like constructs (eval, Function, setTimeout with strings)
  • All loops (for, while, do-while, for-in, for-of)
  • All async/await
  • Dangerous identifiers: process, require, global, __dirname, constructor, __proto__, etc.

SECURE Preset

High security with some flexibility for semi-trusted code.

import { Presets } from 'ast-guard';

const rules = Presets.secured({
  requiredFunctions: ['callTool'],
  maxFunctionCalls: 20,
});

Blocks:

  • All eval-like constructs
  • Dangerous identifiers: process, require, global, __dirname
  • Infinite loops (while, do-while)
  • Async function declarations

Allows:

  • Bounded for/for-of loops
  • Await expressions (but not async functions)

STANDARD Preset

Medium security with sensible defaults for most use cases.

import { Presets } from 'ast-guard';

const rules = Presets.balanced({
  requiredFunctions: ['callTool'],
  additionalDisallowedIdentifiers: ['window', 'document'], // Browser-specific
});

Blocks:

  • eval() and Function constructor
  • Critical identifiers: process, require, eval, Function
  • Infinite while/do-while loops

Allows:

  • All for loops (for, for-in, for-of)
  • Async/await
  • Constructor and prototype access

PERMISSIVE Preset

Low security for internal or trusted scripts.

import { Presets } from 'ast-guard';

const rules = Presets.naive({
  requiredFunctions: ['callTool'], // Optional API enforcement
});

Blocks:

  • eval() only

Allows:

  • All loops
  • Async/await
  • All identifiers
  • Constructor and prototype access

Customizing Presets

All presets accept options to customize their behavior:

import { Presets } from 'ast-guard';

const rules = Presets.secured({
  // Add more blocked identifiers
  additionalDisallowedIdentifiers: ['window', 'document', 'localStorage'],

  // Require specific functions
  requiredFunctions: ['callTool', 'getTool'],
  minFunctionCalls: 1,
  maxFunctionCalls: 50,

  // Override default loop restrictions
  allowedLoops: {
    allowFor: true,
    allowWhile: true, // Override: allow while in secured preset
    allowDoWhile: false,
    allowForIn: false,
    allowForOf: true,
  },

  // Override default async restrictions
  allowAsync: {
    allowAsyncFunctions: true, // Override: allow async in secured preset
    allowAwait: true,
  },

  // Validate specific function arguments
  functionArgumentRules: {
    callTool: {
      minArgs: 2,
      maxArgs: 3,
      expectedTypes: ['string', 'object'],
      validator: (args) => {
        // Custom validation
        if (args[0]?.value === '') {
          return 'Tool name cannot be empty';
        }
        return null;
      },
    },
  },
});

Real-World Examples

Example 1: Untrusted Code Sandbox

import { JSAstValidator, Presets } from 'ast-guard';

// Maximum security for untrusted user code
const validator = new JSAstValidator(
  Presets.strict({
    requiredFunctions: ['callTool'],
    functionArgumentRules: {
      callTool: {
        minArgs: 2,
        expectedTypes: ['string', 'object'],
      },
    },
  }),
);

const userCode = `
  // User-submitted code
  callTool("getData", { id: 123 });
`;

const result = await validator.validate(userCode, {
  rules: {
    'no-eval': true,
    'disallowed-identifier': true,
    'forbidden-loop': true,
    'required-function-call': true,
    'call-argument-validation': true,
  },
});

if (result.valid) {
  // Safe to execute in sandbox
  executeInSandbox(userCode);
}

Example 2: Plugin System

import { JSAstValidator, Presets } from 'ast-guard';

// Balanced security for plugin code
const validator = new JSAstValidator(
  Presets.balanced({
    requiredFunctions: ['registerPlugin'],
    additionalDisallowedIdentifiers: ['process', 'require', 'fs'],
  }),
);

const pluginCode = await loadPluginCode();
const result = await validator.validate(pluginCode, {
  rules: {
    'no-eval': true,
    'disallowed-identifier': true,
    'required-function-call': true,
  },
});

Example 3: Internal Automation Scripts

import { JSAstValidator, Presets } from 'ast-guard';

// Minimal security for trusted internal scripts
const validator = new JSAstValidator(
  Presets.naive({
    requiredFunctions: ['execute'],
  }),
);

Preset Comparison Matrix

Feature STRICT SECURE STANDARD PERMISSIVE
eval()
Function()
for loops
while loops
async/await ⚠️
process
require
global
constructor
Unreachable code ⚠️ ⚠️ ⚠️ ⚠️

Legend: ❌ Blocked (error) | ⚠️ Detected (warning) | ✅ Allowed

AgentScript Globals by Security Level

The AgentScript preset uses security-level-aware globals for defense-in-depth:

Global STRICT SECURE STANDARD PERMISSIVE
Core API
callTool
Data Types
Math, JSON, Array
Object, String, Number, Date
Constants
undefined, NaN, Infinity
Utility Functions
parseInt, parseFloat
isNaN, isFinite
encodeURI, decodeURI
encodeURIComponent, decodeURIComponent
Debugging
console

Always Blocked: eval, Function, process, require, window, globalThis, constructor, __proto__, Proxy, Reflect, Promise, setTimeout, fetch, WebSocket, Map, Set, Symbol, RegExp, and 50+ other dangerous globals.

AgentScript Preset

The AgentScript preset is specifically designed for safe AI agent orchestration. It provides security rules tailored for executing LLM-generated code that calls tools via callTool().

import { JSAstValidator, createAgentScriptPreset } from 'ast-guard';

// Basic usage - secure defaults for AgentScript
const rules = createAgentScriptPreset();
const validator = new JSAstValidator(rules);

// With requireCallTool - enforce that code must call tools
const rules = createAgentScriptPreset({
  requireCallTool: true, // Require at least one callTool() invocation
});

Options:

Option Type Default Description
securityLevel string 'STANDARD' Security level: STRICT, SECURE, STANDARD, PERMISSIVE
requireCallTool boolean false Require at least one callTool() invocation in the code
allowedGlobals string[] (based on securityLevel) Override allowed globals (takes precedence over securityLevel)
additionalDisallowedIdentifiers string[] [] Additional identifiers to block beyond the default set
allowArrowFunctions boolean true Whether to allow arrow functions (for array methods)
allowedLoops object { allowFor, allowForOf } Override default loop restrictions
reservedPrefixes string[] ['__ag_', '__safe_'] Reserved prefixes that user code cannot use
staticCallTarget object { enabled: true } Configuration for static call target validation
callToolValidation object - Validation rules for callTool arguments

Example with all options:

const rules = createAgentScriptPreset({
  // Require the code to actually call tools
  requireCallTool: true,

  // Customize allowed globals
  allowedGlobals: ['callTool', 'getTool', 'Math', 'JSON', 'Array', 'Object'],

  // Block additional identifiers
  additionalDisallowedIdentifiers: ['fetch', 'XMLHttpRequest'],

  // Allow arrow functions for array methods (default: true)
  allowArrowFunctions: true,

  // Allow specific loops (default: for and for-of allowed)
  allowedLoops: {
    allowFor: true,
    allowForOf: true,
    allowWhile: false, // Block while loops
    allowDoWhile: false, // Block do-while loops
    allowForIn: false, // Block for-in loops
  },

  // Static call target validation
  staticCallTarget: {
    enabled: true,
    allowedToolNames: ['users:list', 'users:get'], // Optional whitelist
  },
});

The AgentScript preset is used internally by the enclave-vm package for secure code execution.

Built-in Rules

DisallowedIdentifierRule

Prevents usage of specific identifiers (e.g., eval, Function, process).

import { DisallowedIdentifierRule } from 'ast-guard';

const rule = new DisallowedIdentifierRule({
  disallowed: ['eval', 'Function', 'process', 'require'],
  messageTemplate: 'Access to "{identifier}" is forbidden',
});

ForbiddenLoopRule

Prevents usage of loop constructs. Configurable to allow specific loop types.

import { ForbiddenLoopRule } from 'ast-guard';

const rule = new ForbiddenLoopRule({
  allowFor: false,
  allowWhile: false,
  allowDoWhile: false,
  allowForOf: true, // Allow for-of loops
  allowForIn: false,
  message: 'Loops are not allowed in this context',
});

RequiredFunctionCallRule

Ensures specific functions are called at least once.

import { RequiredFunctionCallRule } from 'ast-guard';

const rule = new RequiredFunctionCallRule({
  required: ['callTool'],
  minCalls: 1,
  maxCalls: 10,
  messageTemplate: 'Must call {function} at least once',
});

Mode Options:

  • mode: 'all' (default) - All functions in required must be called
  • mode: 'any' - At least one function from required must be called
// Require either callTool or invokeAPI to be called
const rule = new RequiredFunctionCallRule({
  required: ['callTool', 'invokeAPI'],
  mode: 'any',
  minCalls: 1,
});

UnreachableCodeRule

Detects unreachable code (code after return/throw/break/continue).

import { UnreachableCodeRule } from 'ast-guard';

const rule = new UnreachableCodeRule();
// Automatically detects unreachable code

CallArgumentValidationRule

Validates function call arguments (count and types).

import { CallArgumentValidationRule } from 'ast-guard';

const rule = new CallArgumentValidationRule({
  functions: {
    callTool: {
      minArgs: 2,
      maxArgs: 2,
      expectedTypes: ['string', 'object'],
    },
    getTool: {
      minArgs: 1,
      maxArgs: 1,
      expectedTypes: ['string'],
      validator: (args, node) => {
        // Custom validation logic
        const firstArg = args[0];
        if (firstArg.type === 'Literal' && firstArg.value === '') {
          return 'Tool name cannot be empty';
        }
        return null; // Valid
      },
    },
  },
});

NoEvalRule

Prevents usage of eval(), Function() constructor, and string-based setTimeout/setInterval.

import { NoEvalRule } from 'ast-guard';

const rule = new NoEvalRule();
// Blocks: eval(), new Function(), setTimeout("code", ...)

NoAsyncRule

Prevents usage of async/await constructs.

import { NoAsyncRule } from 'ast-guard';

const rule = new NoAsyncRule({
  allowAsyncFunctions: false,
  allowAwait: false,
  message: 'Async code is not supported',
});

Custom Rules

Create your own validation rules by implementing the ValidationRule interface:

import { ValidationRule, ValidationContext, ValidationSeverity } from 'ast-guard';
import * as walk from 'acorn-walk';

class NoConsoleRule implements ValidationRule {
  readonly name = 'no-console';
  readonly description = 'Prevents usage of console methods';
  readonly defaultSeverity = ValidationSeverity.WARNING;
  readonly enabledByDefault = true;

  validate(context: ValidationContext): void {
    walk.simple(context.ast as any, {
      MemberExpression: (node: any) => {
        if (node.object.type === 'Identifier' && node.object.name === 'console') {
          context.report({
            code: 'NO_CONSOLE',
            message: 'Console usage is not recommended',
            location: node.loc
              ? {
                  line: node.loc.start.line,
                  column: node.loc.start.column,
                }
              : undefined,
          });
        }
      },
    });
  }
}

// Use your custom rule
const validator = new JSAstValidator([new NoConsoleRule()]);

Configuration

Validator Configuration

const result = await validator.validate(code, {
  // Parse options (passed to acorn)
  parseOptions: {
    ecmaVersion: 'latest',
    sourceType: 'script',
  },

  // Rule configuration
  rules: {
    'disallowed-identifier': true, // Enable with default options
    'no-eval': false, // Disable rule
    'required-function-call': {
      // Enable with custom options
      enabled: true,
      severity: ValidationSeverity.ERROR,
      options: { required: ['callTool'] },
    },
  },

  // Stop on first error
  stopOnFirstError: false,

  // Maximum number of issues (0 = unlimited)
  maxIssues: 100,
});

Rule Management

const validator = new JSAstValidator();

// Register rules
validator.registerRule(new DisallowedIdentifierRule({ disallowed: ['eval'] }));
validator.registerRule(new NoEvalRule());

// Get all rules
const rules = validator.getRules();

// Get specific rule
const rule = validator.getRule('no-eval');

// Unregister rule
validator.unregisterRule('no-eval');

Validation Results

interface ValidationResult {
  valid: boolean; // true if no errors
  issues: ValidationIssue[]; // All issues found
  ast?: acorn.Node; // Parsed AST (if parsing succeeded)
  parseError?: Error; // Parse error (if parsing failed)
}

interface ValidationIssue {
  code: string; // Unique issue code
  severity: ValidationSeverity; // ERROR | WARNING | INFO
  message: string; // Human-readable message
  location?: SourceLocation; // Source location
  data?: Record<string, unknown>; // Additional context
}

Error Handling

All errors extend JSAstValidatorError with specific error types:

import {
  JSAstValidatorError,
  ParseError,
  RuleConfigurationError,
  ConfigurationError,
  RuleNotFoundError,
  InvalidSourceError,
} from 'ast-guard';

try {
  const result = await validator.validate(code);
} catch (error) {
  if (error instanceof ParseError) {
    console.error(`Parse error at line ${error.line}:`, error.message);
  } else if (error instanceof InvalidSourceError) {
    console.error('Invalid source:', error.message);
  } else if (error instanceof RuleConfigurationError) {
    console.error(`Rule ${error.ruleName} misconfigured:`, error.message);
  }
}

Statistics

const result = await validator.validate(code);
const stats = validator.getStats(result, Date.now() - startTime);

console.log(`Total issues: ${stats.totalIssues}`);
console.log(`Errors: ${stats.errors}`);
console.log(`Warnings: ${stats.warnings}`);
console.log(`Duration: ${stats.durationMs}ms`);

Use Cases

1. Sandboxed Code Execution

Validate code before executing in a sandboxed environment:

const validator = new JSAstValidator([
  new DisallowedIdentifierRule({ disallowed: ['eval', 'Function', 'process'] }),
  new NoEvalRule(),
  new ForbiddenLoopRule(), // Prevent infinite loops
  new RequiredFunctionCallRule({ required: ['callTool'] }),
]);

const result = await validator.validate(userCode);
if (result.valid) {
  // Safe to execute
  vm.runInContext(userCode, sandbox);
}

2. API Contract Validation

Ensure code follows API contracts:

const validator = new JSAstValidator([
  new RequiredFunctionCallRule({ required: ['callTool'] }),
  new CallArgumentValidationRule({
    functions: {
      callTool: {
        minArgs: 2,
        expectedTypes: ['string', 'object'],
      },
    },
  }),
]);

3. Security Analysis

Detect potentially dangerous code patterns:

const validator = new JSAstValidator([
  new NoEvalRule(),
  new DisallowedIdentifierRule({
    disallowed: ['eval', 'Function', 'require', 'process', 'child_process'],
  }),
]);

4. Code Quality Checks

Detect code quality issues:

const validator = new JSAstValidator([
  new UnreachableCodeRule(),
  // Add custom rules for your quality standards
]);

Testing

# Run tests
npm test

# Run tests with coverage
npm run test:coverage

Performance

  • Parse time: ~1-5ms for typical scripts (depends on size)
  • Validation time: ~1-10ms per rule (depends on complexity)
  • Memory: Minimal overhead, AST is shared across rules

Comparison

Feature AST Guard ESLint TypeScript
Focus AST validation Code linting Type checking
Extensible ⚠️
Lightweight
Type-safe ⚠️
Security rules ⚠️
Runtime validation

Defense-in-Depth Architecture

AST Guard provides 4 layers of protection:

Layer 1: AST Validation
├── NoEvalRule - Blocks eval(), Function()
├── NoGlobalAccessRule - Blocks window, globalThis, .constructor
├── NoAsyncRule - Blocks async/await (optional)
├── DisallowedIdentifierRule - Blocks dangerous identifiers
└── ForbiddenLoopRule - Blocks/transforms loops

Layer 2: AST Transformation
├── Direct identifiers: console → __safe_console
├── Computed access: obj['eval'] → obj['__safe_eval']
└── Static string literals transformed

Layer 3: Runtime Proxy Layer
├── __safe_callTool() - Validates tool calls
├── __safe_console() - Captures logs
└── All proxied functions enforce security

Layer 4: Worker Isolation
├── Separate worker thread
├── Sandboxed VM context
└── No access to Node.js globals

Known Limitations

These are inherent to any static analyzer (not vulnerabilities):

Limitation Example Mitigation
Computed property access obj['constructor'] Object.freeze(Object.prototype)
Runtime string construction 'con' + 'structor' VM isolation
Destructuring property names { constructor: c } Freeze prototypes

Documentation

Document Purpose
docs/AGENTSCRIPT.md AgentScript language reference for AI agents
docs/CVE-COVERAGE.md Detailed CVE analysis and protection mechanisms
docs/SECURITY-AUDIT.md Full list of 67+ blocked attack vectors
docs/STDLIB-SECURITY.md Standard library security analysis
docs/LOOP-TRANSFORMATION.md Future loop transformation design

Roadmap

  • Core validator architecture
  • Built-in security rules
  • Argument validation
  • Unreachable code detection
  • Comprehensive test suite (613 tests)
  • CVE protection (vm2, isolated-vm, node-vm)
  • Loop transformation (design complete)
  • CLI tool
  • VS Code extension

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for details.

License

Apache-2.0

Credits

Built with: