
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
A production-ready AST security guard for JavaScript - validate, protect, and enforce code safety with extensible rules
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.
| 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:
[].constructor.constructor('code')()__proto__, Object.setPrototypeOfReflect.get, Reflect.constructwindow, globalThis, thisFor detailed CVE analysis, see CVE-COVERAGE.md. For the full list of 100+ blocked attack vectors, see SECURITY-AUDIT.md.
acorn and acorn-walk as dependenciesnpm install ast-guard
# or
yarn add ast-guard
# or
pnpm add ast-guard
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}`);
}
});
}
AST Guard provides pre-configured security presets for quick and easy setup. Choose from five security levels, from maximum lockdown to minimal restrictions.
| 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 |
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);
Maximum security for untrusted code. Bank-grade protection.
Includes all security rules to block known sandbox escape exploits:
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:
eval, Function, setTimeout with strings)process, require, global, __dirname, constructor, __proto__, etc.High security with some flexibility for semi-trusted code.
import { Presets } from 'ast-guard';
const rules = Presets.secured({
requiredFunctions: ['callTool'],
maxFunctionCalls: 20,
});
Blocks:
process, require, global, __dirnameAllows:
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:
process, require, eval, FunctionAllows:
Low security for internal or trusted scripts.
import { Presets } from 'ast-guard';
const rules = Presets.naive({
requiredFunctions: ['callTool'], // Optional API enforcement
});
Blocks:
Allows:
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;
},
},
},
});
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);
}
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,
},
});
import { JSAstValidator, Presets } from 'ast-guard';
// Minimal security for trusted internal scripts
const validator = new JSAstValidator(
Presets.naive({
requiredFunctions: ['execute'],
}),
);
| 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
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.
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.
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',
});
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',
});
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 calledmode: '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,
});
Detects unreachable code (code after return/throw/break/continue).
import { UnreachableCodeRule } from 'ast-guard';
const rule = new UnreachableCodeRule();
// Automatically detects unreachable code
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
},
},
},
});
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", ...)
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',
});
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()]);
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,
});
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');
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
}
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);
}
}
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`);
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);
}
Ensure code follows API contracts:
const validator = new JSAstValidator([
new RequiredFunctionCallRule({ required: ['callTool'] }),
new CallArgumentValidationRule({
functions: {
callTool: {
minArgs: 2,
expectedTypes: ['string', 'object'],
},
},
}),
]);
Detect potentially dangerous code patterns:
const validator = new JSAstValidator([
new NoEvalRule(),
new DisallowedIdentifierRule({
disallowed: ['eval', 'Function', 'require', 'process', 'child_process'],
}),
]);
Detect code quality issues:
const validator = new JSAstValidator([
new UnreachableCodeRule(),
// Add custom rules for your quality standards
]);
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
| Feature | AST Guard | ESLint | TypeScript |
|---|---|---|---|
| Focus | AST validation | Code linting | Type checking |
| Extensible | ✅ | ✅ | ⚠️ |
| Lightweight | ✅ | ❌ | ❌ |
| Type-safe | ✅ | ⚠️ | ✅ |
| Security rules | ✅ | ⚠️ | ❌ |
| Runtime validation | ✅ | ❌ | ❌ |
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
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 |
| 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 |
Contributions are welcome! Please see CONTRIBUTING.md for details.
Apache-2.0
Built with:
FAQs
A production-ready AST security guard for JavaScript - validate, protect, and enforce code safety with extensible rules
The npm package ast-guard receives a total of 505 weekly downloads. As such, ast-guard popularity was classified as not popular.
We found that ast-guard demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.