
Research
/Security News
Mini Shai-Hulud Campaign Hits Red Hat Cloud Services npm Packages
A mini Shai-Hulud campaign compromised Red Hat Cloud Services npm packages to steal developer and CI/CD secrets during installation.
serverless-plugin-module-registry
Advanced tools
A Serverless Framework plugin that scans module registry files and stores feature mappings in DynamoDB for cross-module discovery
A Serverless Framework plugin that scans module registry files, discovers endpoints, and stores module feature mappings in DynamoDB for cross-module discovery and access control.
This plugin enables:
npm install --save-dev serverless-plugin-module-registry
Add the plugin to your serverless.yml:
plugins:
- '@hyperdrive.bot/serverless-composer' # Must be loaded first
- 'serverless-plugin-module-registry' # Load after composer
custom:
moduleRegistry:
tableName: ModuleRegistry # Optional: Custom table name
region: us-east-1 # Optional: Custom region
skipDynamoDB: false # Optional: Skip DynamoDB operations
Each module should have a registry/ folder:
serverless/modules/my-module/
├── functions/ # Existing serverless functions
├── resources/ # Existing serverless resources
└── registry/ # NEW: Registry definitions
├── module.yml # Module metadata
└── features/ # Feature definitions
├── user-management.yml
└── notification-system.yml
registry/module.yml)name: "My Module"
version: "1.0.0"
description: "Sample module for testing"
maintainer: "DevSquad Team"
tags:
- "sample"
- "testing"
registry/features/user-management.yml)name: "User Management"
description: "CRUD operations for user lifecycle management"
version: "1.2.0"
endpoints:
- "POST/users"
- "GET/users"
- "GET/users/*"
- "PUT/users/*"
- "DELETE/users/*"
customPolicies:
- Effect: Allow
Action:
- "execute-api:Invoke"
Resource:
- "arn:aws:execute-api:*:*:*/*/POST/users"
- "arn:aws:execute-api:*:*:*/*/GET/users"
- "arn:aws:execute-api:*:*:*/*/GET/users/*"
- Effect: Allow
Action:
- "dynamodb:PutItem"
- "dynamodb:GetItem"
- "dynamodb:Query"
Resource:
- "arn:aws:dynamodb:*:*:table/Users"
The plugin creates an intermodular ModuleRegistry table using AWS SDK v3 (not CloudFormation) with the following structure:
✅ Intermodular Independence: The table exists independently of any specific module's deployment lifecycle
✅ Cross-Module Sharing: Multiple modules can write to the same registry without ownership conflicts
✅ Deployment Flexibility: The table persists even if individual modules are removed or redeployed
✅ No Stack Dependencies: No CloudFormation stack owns the table, preventing accidental deletion
{service}-{stage}-module-registry (e.g., ds-api-live-module-registry)MODULE#{moduleName} (e.g., MODULE#sign)FEATURE#{featureName} (e.g., FEATURE#user-management)MODULES (for listing all modules)MODULE#{moduleName} (for grouping by module)FEATURES (for listing all features)FEATURE#{featureId} (for direct feature lookup by ID){
"PK": "MODULE#sign",
"SK": "FEATURE#user-management",
"GSI1PK": "MODULES",
"GSI1SK": "MODULE#sign",
"moduleName": "sign",
"featureName": "user-management",
"description": "CRUD operations for user lifecycle",
"version": "1.2.0",
"endpoints": [
"POST/users",
"GET/users",
"GET/users/*"
],
"customPolicies": [...],
"lastUpdated": "2025-01-01T00:00:00Z"
}
registry/ folderThe stored data supports these query patterns:
// Query GSI1 with GSI1PK = "MODULES"
const modules = await dynamoClient.query({
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk',
ExpressionAttributeValues: {
':pk': 'MODULES'
}
})
// Query main table with PK = "MODULE#{name}"
const features = await dynamoClient.query({
KeyConditionExpression: 'PK = :pk',
ExpressionAttributeValues: {
':pk': `MODULE#${moduleName}`
}
})
// Get item with PK + SK
const feature = await dynamoClient.getItem({
Key: {
PK: `MODULE#${moduleName}`,
SK: `FEATURE#${featureName}`
}
})
// Query GSI2 with featureId
const feature = await dynamoClient.query({
IndexName: 'GSI2',
KeyConditionExpression: 'GSI2PK = :pk AND GSI2SK = :sk',
ExpressionAttributeValues: {
':pk': 'FEATURES',
':sk': `FEATURE#${featureId}`
}
})
| Option | Type | Default | Description |
|---|---|---|---|
tableName | string | {service}-{stage}-module-registry | DynamoDB table name |
region | string | provider.region | AWS region for DynamoDB |
skipDynamoDB | boolean | false | Skip DynamoDB operations |
strict | boolean | false | Enforce registry folders on all modules |
strict: true)Controls whether the plugin should enforce registry compliance:
strict: false (Default - Graceful Mode)
custom:
moduleRegistry:
strict: false # Graceful: skip modules without registry folders
strict: true (Enforcement Mode)
custom:
moduleRegistry:
strict: true # Strict: error if modules are missing registry folders
Use strict: false when:
Use strict: true when:
The plugin provides detailed logging with [module-registry] prefix:
serverless deploy --verbose
Example output:
[module-registry] 🔍 Scanning modules for registry definitions...
[module-registry] Found 3 modules: sample, sign, agents
[module-registry] 📋 Processing registry for module: sample
[module-registry] Found 2 feature definitions
[module-registry] ✓ user-management: 5 endpoints
[module-registry] ✓ notification-system: 9 endpoints
[module-registry] ✅ Registry processing complete. Found 4 features across 3 modules
No need for separate service files! Import service functions directly from the plugin:
// In your handlers - import from the generated service package
import {
listAllModules,
getModuleFeatures,
getFeatureDetails,
createModuleRegistryLogger,
type ModuleInfo,
type FeatureInfo
} from 'module-registry'
const logger = createModuleRegistryLogger('my-handler')
export const myHandler = async (event: any) => {
const modules = await listAllModules()
logger.info(`Found ${modules.length} modules`)
return { modules }
}
// Example: Get feature by ID without knowing module
export const getFeatureHandler = async (event: any) => {
const { featureId } = event.pathParameters
const feature = await getFeatureById(featureId)
if (!feature) {
return { statusCode: 404, body: { error: 'Feature not found' } }
}
return { statusCode: 200, body: feature }
}
// In your handlers - require from the generated service package
const {
listAllModules,
getModuleFeatures,
createModuleRegistryLogger
} = require('module-registry')
const logger = createModuleRegistryLogger('my-handler')
exports.myHandler = async (event) => {
const modules = await listAllModules()
logger.info(`Found ${modules.length} modules`)
return { modules }
}
| Function | Description | Returns |
|---|---|---|
listAllModules() | List all deployed modules | Promise<ModuleInfo[]> |
getModuleFeatures(moduleName) | Get features for a module | Promise<FeatureInfo[]> |
getFeatureDetails(moduleName, featureName) | Get feature details | Promise<FeatureDetails | null> |
getFeatureById(featureId) | Get feature by ID only (uses GSI2) | Promise<FeatureDetails | null> |
getModuleMetadata(moduleName) | Get module metadata only | Promise<ModuleInfo | null> |
getAllEndpoints() | Get all endpoints across modules | Promise<EndpointInfo[]> |
createModuleRegistryLogger(context) | Create logger for service functions | Logger |
// src/handlers/core/module-registry/listModules.js
const { http } = require('../../middlewares/http')
const { listAllModules, createModuleRegistryLogger } = require('serverless-plugin-module-registry')
const logger = createModuleRegistryLogger('list-modules-handler')
export const listModules = http(async (event) => {
logger.info('Listing all deployed modules', {
userSub: event.requestContext.authorizer?.claims?.sub
})
try {
const modules = await listAllModules()
logger.info(`Successfully retrieved ${modules.length} modules`)
return {
statusCode: 200,
body: {
modules,
count: modules.length,
timestamp: new Date().toISOString()
}
}
} catch (error) {
logger.error('Error listing modules:', error.message)
return {
statusCode: 500,
body: {
error: 'Failed to list modules',
message: error.message
}
}
}
})
The plugin provides powerful CLI commands for managing module registries:
registryGenerate)Automatically generate registry files for a module using AI analysis:
# Generate registry for a specific module
serverless registryGenerate --module workforce
# Force overwrite existing registry files
serverless registryGenerate --module workforce --force
Prerequisites:
OPENROUTER_API_KEY environment variable for AI generationaws_iam authorizer are included in registryWhat it does:
registry/module.yml and registry/features/*.yml filesEmployeeReadAccess, UserSelfService)registryGeneratePackage)Generate the virtual service package for importing registry functions:
# Generate service package
serverless registryGeneratePackage
# Force regeneration
serverless registryGeneratePackage --force --verbose
What it creates:
node_modules/module-registry/service.js - Service functionsnode_modules/module-registry/service.d.ts - TypeScript definitionsnode_modules/module-registry/env.js - Environment configurationnode_modules/module-registry/package.json - Package manifest# Install dependencies
npm install
# Build the plugin
npm run build
# Watch for changes during development
npm run watch
# Run type checking
npm run typecheck
# Run linting
npm run lint
The project uses tsup for building:
src/index.ts (main plugin), src/service.ts (service functions)dist/ directory with CommonJS and TypeScript definitionsTests are currently disabled but the framework is set up with Jest:
# Tests are disabled - would run with:
npm test # Currently outputs: "Tests disabled"
Plugin not found
Error: Plugin "serverless-plugin-module-registry" not found
Solution: Ensure plugin is installed and listed in serverless.yml plugins section
Variable resolution errors
Module Registry: Unresolved variables in configuration
Solution: Check that all variables in custom.moduleRegistry section can be resolved
DynamoDB table creation fails
Failed to create table: AccessDenied
Solution: Ensure AWS credentials have DynamoDB permissions:
dynamodb:CreateTabledynamodb:DescribeTabledynamodb:UpdateContinuousBackupsdynamodb:TagResourceAI generation fails
OPENROUTER_API_KEY environment variable is required
Solution: Set OpenRouter API key: export OPENROUTER_API_KEY=your_key_here
Registry generation requires function descriptions
Function 'myFunction' is missing required 'description' field
Solution: Add descriptions to all functions in your serverless function definitions
Enable detailed logging:
serverless deploy --verbose
Look for [module-registry] prefixed logs for plugin-specific information.
The module registry provides a utility function to create IAM roles for tenant onboarding with ABAC (Attribute-Based Access Control) tags.
import { createTenantRoles } from 'serverless-plugin-module-registry/tenant'
// During tenant onboarding
const roleArns = await createTenantRoles({
tenantId: 'acme',
identityPoolId: 'us-east-1:12345678-1234-1234-1234-123456789012',
modules: ['sign', 'workforce'], // Optional: filter to specific modules
config: {
tableName: 'ModuleRegistry',
region: 'us-east-1',
policyPrefix: 'api'
},
logger: {
info: (msg, data) => console.log(msg, data),
warning: (msg, data) => console.warn(msg, data),
error: (msg, err) => console.error(msg, err)
}
})
console.log(roleArns)
// {
// authenticated: "arn:aws:iam::123456789012:role/api-acme-authenticated",
// unauthenticated: "arn:aws:iam::123456789012:role/api-acme-unauthenticated"
// }
Roles follow the pattern: {policyPrefix}-{tenantId}-{roleType}
Examples:
api-acme-authenticatedapi-acme-unauthenticatedapi-acme-adminEach role is tagged with module features in the format:
{moduleName}Features: "feature1:feature2:feature3"
Example tags:
signFeatures: "7tmARMbS:BRUBT2SN:F8d3wY_v"workforceFeatures: "abc123:def456:ghi789"The function is idempotent - it can be called multiple times safely:
strict: true) in productiongit checkout -b feature/my-featureWhen contributing:
| Plugin Version | Serverless Framework | Node.js |
|---|---|---|
| 1.0.x | 2.x, 3.x, 4.x | ≥14.0 |
MIT License - see the LICENSE file for details.
FAQs
A Serverless Framework plugin that scans module registry files and stores feature mappings in DynamoDB for cross-module discovery
We found that serverless-plugin-module-registry 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
/Security News
A mini Shai-Hulud campaign compromised Red Hat Cloud Services npm packages to steal developer and CI/CD secrets during installation.

Research
/Security News
The North Korean malware loader hides in a Packagist-listed package and its GitHub branch to fetch and execute remote code in a likely Contagious Interview-style lure.

Security News
The Rust project is moving toward formal rules on LLM use in contributions after months of internal debate over maintainer burden, code quality, and contributor experience.