ESLint rules for type-safe LLM context management
npm install @mullion/eslint-plugin---
``bash`
npm install @mullion/eslint-plugin --save-dev
This ESLint plugin provides static analysis rules to catch context leaks and enforce confidence checking in Mullion applications. It helps prevent security vulnerabilities by detecting when LLM-generated values cross scope boundaries without proper bridging.
LLM applications deal with data that crosses trust boundaries. Without static analysis, it's easy to accidentally:
- Leak privileged data into public scopes (admin → customer)
- Mix tenant data in multi-tenant systems
- Use low-confidence outputs for critical decisions without validation
- Lose audit trails when data flows between contexts
This plugin catches these issues at compile-time with zero runtime overhead.
| Risk | Without Plugin | With Plugin |
| -------------------- | --------------------------------- | --------------------- |
| Context leaks | Discovered in production | Caught in IDE/CI |
| Low confidence usage | Runtime errors or silent failures | Compile-time warnings |
| Audit trail gaps | Manual code review needed | Automatic enforcement |
| Security review time | Hours per PR | Seconds (automated) |
`typescript
// ❌ Without plugin: This compiles but leaks admin data
let adminData;
await client.scope('admin', async (ctx) => {
adminData = await ctx.infer(SecretSchema, sensitiveDoc);
});
await client.scope('public', async (ctx) => {
return adminData.value; // BUG: No warning!
});
// ✅ With plugin: ESLint error prevents compilation
// "Context leak detected: 'adminData' crosses scope boundary"
`
`javascript
// eslint.config.js
import mullion from '@mullion/eslint-plugin';
export default [
{
plugins: {
'@mullion': mullion,
},
rules: {
'@mullion/no-context-leak': 'error',
'@mullion/no-sink-leak': 'error',
'@mullion/require-confidence-check': 'warn',
},
},
];
`
`javascript`
{
"plugins": ["@mullion"],
"rules": {
"@mullion/no-context-leak": "error",
"@mullion/no-sink-leak": "error",
"@mullion/require-confidence-check": "warn"
}
}
`javascript
// Use recommended configuration
import mullion from '@mullion/eslint-plugin';
export default [
...mullion.configs.recommended
];
// Or use strict configuration
export default [
...mullion.configs.strict
];
`
Prevents accidental context leaks when LLM-generated values cross scope boundaries without proper bridging.
#### ❌ Incorrect
`typescript
let leaked;
await client.scope('admin', async (ctx) => {
leaked = await ctx.infer(Schema, 'secret data'); // 🚨 ESLint error
});
await client.scope('public', async (ctx) => {
return leaked.value; // 🚨 ESLint error
});
`
`typescript
await client.scope('scope-a', async (ctxA) => {
const dataA = await ctxA.infer(Schema, input);
await client.scope('scope-b', async (ctxB) => {
return dataA.value; // 🚨 ESLint error - cross-scope usage
});
});
`
#### ✅ Correct
`typescript
await client.scope('admin', async (adminCtx) => {
const adminData = await adminCtx.infer(Schema, 'secret data');
await client.scope('public', async (publicCtx) => {
const bridged = publicCtx.bridge(adminData); // ✅ Explicit bridge
return bridged.value;
});
});
`
`typescript`
await client.scope('scope-a', async (ctxA) => {
const dataA = await ctxA.infer(Schema, input);
return ctxA.use(dataA); // ✅ Safe - same scope
});
Prevents scoped values from being sent to logs, traces, or error-reporting sinks without explicit redaction.
#### ❌ Incorrect
`typescript
const result = await ctx.infer(Schema, input);
console.log(result); // 🚨 ESLint error
logger.info(result.value); // 🚨 ESLint error
span.setAttribute('mullion.input', result); // 🚨 ESLint error
`
#### ✅ Correct
`typescript
import {redact, summarize} from '@mullion/core';
const result = await ctx.infer(Schema, input);
console.log(redact(result)); // ✅ Safe summary
span.setAttribute('mullion.input', summarize(result)); // ✅ Safe summary
`
Warns when LLM-generated values are used without checking their confidence scores.
#### ❌ Triggers Warning
`typescript
await client.scope('processing', async (ctx) => {
const result = await ctx.infer(Schema, input);
// ⚠️ ESLint warning - using value without confidence check
if (result.value.category === 'important') {
processImportantData(result.value);
}
return result.value; // ⚠️ ESLint warning
});
`
#### ✅ Correct
`typescript
await client.scope('processing', async (ctx) => {
const result = await ctx.infer(Schema, input);
// ✅ Check confidence before use
if (result.confidence >= 0.8 && result.value.category === 'important') {
processImportantData(result.value);
}
// ✅ Or use a handler function
return handleResult(result); // Function receives full Owned object
});
`
#### no-context-leak
`javascript`
{
"@mullion/no-context-leak": ["error", {
"allowBridge": true, // Allow ctx.bridge() calls (default: true)
"strictMode": false // Stricter checking (default: false)
}]
}
#### no-sink-leak
`javascript`
{
"@mullion/no-sink-leak": "error"
}
#### require-confidence-check
`javascript`
{
"@mullion/require-confidence-check": ["warn", {
"minConfidence": 0.8, // Minimum confidence threshold
"requireExplicit": false, // Require explicit .confidence checks
"allowHandlers": true // Allow functions that handle Owned values
}]
}
#### recommended
`javascript`
{
rules: {
'@mullion/no-context-leak': 'error',
'@mullion/no-sink-leak': 'error',
'@mullion/require-confidence-check': 'warn'
}
}
#### strict
`javascript`
{
rules: {
'@mullion/no-context-leak': 'error',
'@mullion/no-sink-leak': 'error',
'@mullion/require-confidence-check': 'error' // More strict
}
}
#### Customer Support Pipeline
`typescript
// ❌ BAD: Customer data leaks to admin scope
let customerQuery;
await client.scope('customer', async (ctx) => {
customerQuery = await ctx.infer(QuerySchema, userInput); // 🚨 Leak
});
await client.scope('admin', async (ctx) => {
// This could expose customer PII to admin systems
return analyzeWithAdminTools(customerQuery.value); // 🚨 Leak
});
// ✅ GOOD: Explicit bridging
await client.scope('customer', async (customerCtx) => {
const query = await customerCtx.infer(QuerySchema, userInput);
return await client.scope('admin', async (adminCtx) => {
const bridged = adminCtx.bridge(query); // ✅ Tracked transfer
return analyzeWithAdminTools(bridged.value);
});
});
`
#### Multi-Tenant Data Processing
`typescript
// ❌ BAD: Tenant data cross-contamination
const results = [];
for (const tenant of tenants) {
await client.scope(tenant-${tenant.id}, async (ctx) => {
const data = await ctx.infer(Schema, tenant.input);
results.push(data); // 🚨 Mixing tenant scopes!
});
}
// ✅ GOOD: Keep tenant data isolated
const results = [];
for (const tenant of tenants) {
const result = await client.scope(tenant-${tenant.id}, async (ctx) => {`
const data = await ctx.infer(Schema, tenant.input);
return ctx.use(data); // ✅ Extract safely within scope
});
results.push({tenantId: tenant.id, data: result});
}
#### ❌ Common Anti-Patterns
`typescript
// Direct value access without confidence check
const result = await ctx.infer(Schema, input);
const decision = result.value.decision; // ⚠️ Warning
// Implicit confidence in conditional
if (result.value.important) {
// ⚠️ Warning
processImportant();
}
`
#### ✅ Good Patterns
`typescript
// Explicit confidence checking
const result = await ctx.infer(Schema, input);
if (result.confidence >= 0.9) {
const decision = result.value.decision; // ✅ Safe
}
// Handler function approach
function handleClassification(owned: Owned
if (owned.confidence < 0.8) {
return escalateToHuman(owned);
}
return processAutomatically(owned.value);
}
const result = await ctx.infer(Schema, input);
handleClassification(result); // ✅ Handler receives full context
`
The plugin leverages TypeScript's type system for accurate detection:
`typescript
// The plugin understands scope types
type AdminData = Owned
type UserData = Owned
// It detects type mismatches
function processUserData(data: UserData) {
/ ... /
}
const adminData: AdminData = await adminCtx.infer(SecretSchema, input);
processUserData(adminData); // 🚨 Type and scope mismatch detected
`
#### False Positives
If you get false positives, you might need to:
1. Update TypeScript configuration:
`json`
{
"parserOptions": {
"project": "./tsconfig.json"
}
}
2. Ensure proper imports:
`typescript`
import type {Owned, Context} from '@mullion/core';
#### Missing Violations
If leaks aren't being caught:
1. Check TypeScript types are available
2. Verify the plugin can access type information
3. Ensure you're using the correct scope patterns
Enable debug mode to see what the plugin is detecting:
`bash`
DEBUG=@mullion/eslint-plugin eslint your-file.ts
See the basic example which demonstrates both correct usage and intentional violations that ESLint catches.
Run the example:
`bash``
cd examples/basic
npm install
npm run lint # See violations caught
npm run demo # See proper usage
Found a bug or want to add a rule? See CONTRIBUTING.md for guidelines.
MIT - see LICENSE for details.