
Research
Two Malicious Rust Crates Impersonate Popular Logger to Steal Wallet Keys
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
flowlock-plugin-sdk
Advanced tools
Plugin SDK for extending FlowLock with custom validation checks.
The plugin SDK allows developers to create custom validation checks that integrate seamlessly with FlowLock's validation system. Plugins can add domain-specific validations, integrate with external systems, or enforce custom business rules.
npm install flowlock-plugin-sdk
import { Plugin, PluginContext, Issue } from 'flowlock-plugin-sdk';
export const myPlugin: Plugin = {
name: 'MY_CUSTOM_CHECK',
description: 'Validates custom business rules',
async run(context: PluginContext): Promise<Issue[]> {
const { spec, options } = context;
const issues: Issue[] = [];
// Perform validation
spec.entities.forEach(entity => {
if (!entity.description) {
issues.push({
code: 'MISSING_DESCRIPTION',
severity: 'warning',
message: `Entity '${entity.id}' is missing a description`,
path: `entities[${entity.id}]`
});
}
});
return issues;
}
};
import { Plugin, PluginContext, Issue } from 'flowlock-plugin-sdk';
export const autoFixPlugin: Plugin = {
name: 'AUTO_FIX_EXAMPLE',
description: 'Example plugin with auto-fix capability',
async run(context: PluginContext): Promise<Issue[]> {
const { spec, options } = context;
const issues: Issue[] = [];
spec.screens.forEach((screen, index) => {
if (!screen.id) {
issues.push({
code: 'MISSING_SCREEN_ID',
severity: 'error',
message: `Screen '${screen.name}' is missing an ID`,
path: `screens[${index}]`,
fix: {
description: 'Generate ID from name',
apply: (spec) => {
spec.screens[index].id = screen.name
.toLowerCase()
.replace(/\s+/g, '-');
return spec;
}
}
});
}
});
return issues;
}
};
import { Plugin, PluginContext, Issue } from 'flowlock-plugin-sdk';
import axios from 'axios';
export const apiValidatorPlugin: Plugin = {
name: 'API_VALIDATOR',
description: 'Validates API endpoints exist',
async run(context: PluginContext): Promise<Issue[]> {
const { spec, options } = context;
const issues: Issue[] = [];
const baseUrl = options.apiBaseUrl || 'http://localhost:3000';
// Check each screen's route
for (const screen of spec.screens) {
if (screen.route) {
try {
// Convert route pattern to test URL
const testUrl = `${baseUrl}${screen.route.replace(/:[^/]+/g, 'test')}`;
await axios.head(testUrl);
} catch (error) {
issues.push({
code: 'ROUTE_NOT_FOUND',
severity: 'error',
message: `Route '${screen.route}' for screen '${screen.id}' is not accessible`,
path: `screens[${screen.id}].route`
});
}
}
}
return issues;
}
};
interface Plugin {
name: string; // Unique check name (UPPER_SNAKE_CASE)
description: string; // Human-readable description
run: (context: PluginContext) => Promise<Issue[]>;
enabled?: boolean; // Default enabled state
level?: 'basic' | 'enhanced' | 'strict'; // Validation level
}
interface PluginContext {
spec: UXSpec; // The UX specification to validate
options: PluginOptions; // Plugin-specific options
helpers: PluginHelpers; // Utility functions
}
interface PluginOptions {
fix?: boolean; // Enable auto-fix
quiet?: boolean; // Suppress output
[key: string]: any; // Custom options
}
interface PluginHelpers {
// Get all entity fields including derived
getEntityFields(entityId: string): Field[];
// Check if field exists in entity
hasField(entityId: string, fieldId: string): boolean;
// Get all screens for a role
getScreensForRole(role: string): Screen[];
// Find flows that write to entity
getFlowsWritingToEntity(entityId: string): Flow[];
// Parse entity.field notation
parseFieldReference(ref: string): { entity: string; field: string };
}
interface Issue {
code: string; // Error code (UPPER_SNAKE_CASE)
severity: 'error' | 'warning' | 'info';
message: string; // Human-readable message
path?: string; // JSON path to issue location
fix?: { // Optional auto-fix
description: string; // What the fix does
apply: (spec: UXSpec) => UXSpec; // Function to apply fix
};
}
import { registerPlugin } from 'flowlock-plugin-sdk';
import { myPlugin } from './my-plugin';
// Register plugin
registerPlugin(myPlugin);
// Run checks with plugin
import { runChecks } from 'flowlock-checks-core';
const results = await runChecks(spec);
Create a flowlock.config.json
:
{
"plugins": [
"./plugins/my-plugin.js",
"@company/flowlock-plugin-security",
{
"path": "./plugins/api-validator.js",
"options": {
"apiBaseUrl": "https://api.example.com"
}
}
]
}
// Good
`Entity '${entity.id}' field '${field.id}' uses reserved name`
// Bad
`Invalid field name`
// Good - specific path
path: `entities[2].fields[0].type`
// Bad - vague path
path: `entities`
fix: {
description: 'Add missing required field',
apply: (spec) => {
spec.entities[index].fields.push({
id: 'id',
type: 'string',
required: true
});
return spec;
}
}
async run(context: PluginContext): Promise<Issue[]> {
try {
// Validation logic
return issues;
} catch (error) {
return [{
code: 'PLUGIN_ERROR',
severity: 'error',
message: `Plugin failed: ${error.message}`
}];
}
}
export const businessRulePlugin: Plugin = {
name: 'BUSINESS_RULES',
description: 'Validates company-specific business rules',
async run(context: PluginContext): Promise<Issue[]> {
const issues: Issue[] = [];
const { spec } = context;
// All customer-facing screens must have help text
spec.screens
.filter(s => s.role === 'customer')
.forEach(screen => {
if (!screen.helpText) {
issues.push({
code: 'MISSING_HELP_TEXT',
severity: 'warning',
message: `Customer screen '${screen.id}' needs help text`,
path: `screens[${screen.id}].helpText`
});
}
});
// All forms must have validation rules
spec.screens.forEach(screen => {
screen.forms?.forEach(form => {
if (!form.validation) {
issues.push({
code: 'MISSING_VALIDATION',
severity: 'error',
message: `Form '${form.id}' needs validation rules`,
path: `screens[${screen.id}].forms[${form.id}].validation`
});
}
});
});
return issues;
}
};
export const performancePlugin: Plugin = {
name: 'PERFORMANCE',
description: 'Validates performance best practices',
async run(context: PluginContext): Promise<Issue[]> {
const issues: Issue[] = [];
const { spec } = context;
// Warn about lists without pagination
spec.screens.forEach(screen => {
screen.lists?.forEach(list => {
if (!list.paginated) {
issues.push({
code: 'UNPAGINATED_LIST',
severity: 'warning',
message: `List '${list.id}' should use pagination for performance`,
path: `screens[${screen.id}].lists[${list.id}].paginated`,
fix: {
description: 'Enable pagination with default page size of 20',
apply: (spec) => {
const screenIndex = spec.screens.findIndex(s => s.id === screen.id);
const listIndex = spec.screens[screenIndex].lists.findIndex(l => l.id === list.id);
spec.screens[screenIndex].lists[listIndex].paginated = true;
spec.screens[screenIndex].lists[listIndex].pageSize = 20;
return spec;
}
}
});
}
});
});
return issues;
}
};
import { myPlugin } from './my-plugin';
import { createTestContext } from 'flowlock-plugin-sdk/testing';
describe('My Plugin', () => {
it('should detect missing descriptions', async () => {
const spec = {
entities: [
{ id: 'User', fields: [] }
],
screens: [],
flows: []
};
const context = createTestContext(spec);
const issues = await myPlugin.run(context);
expect(issues).toHaveLength(1);
expect(issues[0].code).toBe('MISSING_DESCRIPTION');
});
});
See the main repository for contribution guidelines.
MIT
FAQs
Plugin SDK for FlowLock
We found that flowlock-plugin-sdk 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.
Research
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
Research
A malicious package uses a QR code as steganography in an innovative technique.
Research
/Security News
Socket identified 80 fake candidates targeting engineering roles, including suspected North Korean operators, exposing the new reality of hiring as a security function.