@sunrun-security/sr-sec-ts-sns-logger
v2.1.2
Published
TypeScript/JavaScript security logging module for sending structured security logs to AWS SNS
Readme
sr-sec-ts-sns-logger: Developer Implementation Guide
A TypeScript/JavaScript module for sending structured security logs to AWS SNS for centralized security monitoring. This guide takes you from initial setup to a fully instrumented, production-ready implementation.
Key Features:
- 🔒 Security-First: Designed specifically for security event logging
- 🚀 Production-Ready: Built-in retry logic, timeouts, and error handling using AWS SDK v3
- ✅ Compliance: Covers all High priority security events with required data points
- 🧪 Test Mode: Test without AWS credentials for development and validation
- 📦 Simple API: One-line initialization, clean function calls with TypeScript support
- 🛡️ Validation: Comprehensive validation with detailed error messages showing all missing fields
- 📋 Schema Compliant: Implements standardized flat JSON structure with all required fields
- ⚡ Non-Blocking: Fire-and-forget pattern prevents application blocking
- 🎯 Type Safe: Full TypeScript support with comprehensive type definitions
- 📊 Auto-Batching: Automatically handles large record lists that exceed SNS message limits
- 🔍 Context Extraction: Automatically captures function/file name for enhanced debugging
📑 Table of Contents
Getting Started
Part 1: Quick Start (5-Minute Goal)
Before Production Implementation
Part 2: The Implementation Playbook
- Identify Security-Relevant Activities
- Map Activities to Logger Functions
- Track Your Implementation Progress
Part 3: API Function Reference
- Authentication & Session Events
- Authorization & Access Events
- API & Data Access Events
- Key Configuration & Security Changes
Part 3.5: Auto-Context Extraction Helpers
Part 4: Production Readiness
Appendix
- Schema Structure & Fields
- Sample Log Outputs
- Migration from Python Version
- Error Handling & Troubleshooting
- Module Structure
- TypeScript Support
- Testing & Development
🎯 Why Security Logging Matters
Implementing comprehensive security logging is not just a compliance requirement—it's a critical foundation for mature cybersecurity operations. This package enables organizations to:
🔍 Mature Risk Detection Methods
- Build baseline behavior patterns to identify anomalous activities
- Enable advanced threat hunting and security analytics
- Support machine learning-based security detection systems
- Create comprehensive audit trails for forensic investigations
🛡️ Insider Threat Prevention
- Monitor privileged user activities and administrative actions
- Track data access patterns to detect unauthorized behavior
- Identify policy violations and suspicious access attempts
- Enable real-time alerting on high-risk activities
🏢 Poaching Risk Reduction
- Log customer data access to prevent unauthorized data harvesting
- Track bulk data exports and multi-record access patterns
- Monitor user behavior changes that may indicate malicious intent
- Provide evidence for incident response and legal proceedings
Without proper security logging, organizations operate blind to internal threats, compliance violations, and sophisticated attacks that bypass perimeter defenses.
Part 1: Quick Start (5-Minute Goal)
This section's goal is to verify your setup and send your first log. We will log a simple user login event to confirm that the pipeline is working correctly.
Step 1: Install and Build
Install from npm (Public Package)
The package is published publicly on npm and requires no authentication tokens or special registry configuration.
npm install @sunrun-security/sr-sec-ts-sns-loggerThat's it -- no PAT, no .npmrc configuration, no GitHub Packages setup required.
Install Dependencies in Your Application
Ensure you have the AWS SDK v3 installed:
npm install @aws-sdk/client-sns @aws-sdk/client-cloudwatchStep 2: Initialize the Logger
In your application's main entry point (e.g., index.ts, app.ts), initialize the logger once.
⚠️ CRITICAL: For all development and testing, use testMode: true. This prints logs to the console instead of sending them to the security team's AWS SNS topic.
import * as SecurityLogging from '@sunrun-security/sr-sec-ts-sns-logger';
// Initialize once at application startup with environment configuration
SecurityLogging.initSecurityLogging({
testMode: process.env.NODE_ENV !== 'production', // Use testMode for all non-production
// Set environment fields ONCE - automatically included in all subsequent logs
cloudEnvType: SecurityLogging.CloudEnvType.PROD, // Or CloudEnvType.STAGE, DEV, TEST
cloudEnvUniqueId: process.env.AWS_ACCOUNT_ID, // AWS Account ID
cloudEnvName: process.env.ENVIRONMENT || 'production', // Human-readable environment name
serviceAccountId: process.env.AWS_EXECUTION_ROLE_ARN, // IAM role/service account ARN
serviceName: 'my-application', // Your application name
});
console.log('✅ Security logging initialized.');Production Configuration:
// In production with environment config (recommended)
SecurityLogging.initSecurityLogging({
cloudEnvType: SecurityLogging.CloudEnvType.PROD,
cloudEnvUniqueId: process.env.AWS_ACCOUNT_ID!,
cloudEnvName: 'production',
serviceAccountId: process.env.AWS_EXECUTION_ROLE_ARN!,
serviceName: 'my-application',
});Benefit: Once these fields are set during initialization, you don't need to pass them with every log call - they're automatically included!
Multi-Region Failover Configuration (Optional but Recommended):
For high availability, you can enable automatic failover to a secondary AWS region:
// With failover enabled (automatically fails over to us-east-2)
SecurityLogging.initSecurityLogging({
topicArn: "arn:aws:sns:us-west-2:123456789012:sr-sec-logging-log-topic-dev",
failoverTopicArn: "arn:aws:sns:us-east-2:123456789012:sr-sec-logging-log-topic-failover-dev",
enableFailover: true // Default: true
});How Failover Works:
- Primary region (us-west-2) is tried first with fast timeout (2 seconds)
- If primary fails with retriable error, automatically switches to failover region (us-east-2)
- Circuit breaker prevents repeated attempts to failing regions
- Automatic recovery when primary region becomes healthy
- Zero code changes required - completely transparent to your application
Environment Variables (Alternative Configuration):
export SECURITY_LOGS_TOPIC_ARN="arn:aws:sns:us-west-2:123456789012:my-topic"
export SECURITY_LOGS_FAILOVER_TOPIC_ARN="arn:aws:sns:us-east-2:123456789012:my-failover-topic"
export AWS_REGION="us-west-2"
export SECURITY_LOGS_FAILOVER_REGION="us-east-2"IAM User Credentials (For Applications Not Using IAM Roles):
If your application uses IAM User credentials (static access keys) instead of IAM Roles (Lambda execution roles, ECS task roles, etc.), you can provide credentials directly:
// With explicit IAM User credentials
SecurityLogging.initSecurityLogging({
credentials: {
accessKeyId: process.env.MY_AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.MY_AWS_SECRET_ACCESS_KEY!,
// sessionToken: process.env.AWS_SESSION_TOKEN, // Optional, for temporary credentials
},
testMode: process.env.NODE_ENV !== 'production',
});Note: If credentials are not provided, the AWS SDK uses the default credential chain (environment variables
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY, IAM roles, credential files, etc.)
Required IAM Policy for IAM Users:
If using IAM User credentials to publish to a cross-account SNS topic, the IAM User needs this policy attached:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSNSPublishToSecurityLogging",
"Effect": "Allow",
"Action": "sns:Publish",
"Resource": [
"arn:aws:sns:us-west-2:687126124183:sr-sec-logging-log-topic-dev",
"arn:aws:sns:us-west-2:000576341507:sr-sec-logging-log-topic-prod",
"arn:aws:sns:us-east-2:000576341507:sr-sec-logging-log-topic-failover-prod"
]
},
{
"Sid": "AllowKMSForSNS",
"Effect": "Allow",
"Action": ["kms:GenerateDataKey*", "kms:Decrypt"],
"Resource": [
"arn:aws:kms:us-west-2:687126124183:key/*",
"arn:aws:kms:us-west-2:000576341507:key/*",
"arn:aws:kms:us-east-2:000576341507:key/*"
],
"Condition": {
"StringEquals": {
"kms:ViaService": [
"sns.us-west-2.amazonaws.com",
"sns.us-east-2.amazonaws.com"
]
}
}
}
]
}Monitoring Failover:
// Get failover metrics
const metrics = SecurityLogging.getFailoverMetrics();
console.log(`Primary success: ${metrics.primary_success}`);
console.log(`Failover used: ${metrics.failover_success} times`);
console.log(`Total failures: ${metrics.total_failures}`);
// Reset metrics (useful for periodic monitoring)
SecurityLogging.resetFailoverMetrics();Step 3: Log Your First Event
Now that the logger is initialized, it's time to add your first security log. This step will guide you through identifying where to instrument your code and understanding where each value comes from.
📍 Step 3a: Locate Your Login Handler
First, identify where user authentication happens in your application. Common locations include:
- Express/Node.js: Login route handler (e.g.,
POST /auth/login) - Next.js with NextAuth:
[...nextauth].tscallbacks or custom signin handler - AWS Lambda: Authorization/authentication Lambda functions
- Other frameworks: Wherever your application verifies credentials and creates a session
📖 Step 3b: Review the logUserLogin Function
Before implementing, review the logUserLogin function definition to understand all available parameters:
👉 View logUserLogin in security-logging-sns.ts (Search for "Authentication & Session Events")
This shows you:
- Required vs optional parameters
- Validation rules
- Expected data types
- Available constants
💡 Step 3c: Understand Where Values Come From
Before writing code, map out where you'll get each required value:
| Parameter | Where to Find It | Example Source |
|-----------|-----------------|----------------|
| actor_identifier | User's email/username from your auth system | user.email, session.user.email, decoded JWT sub |
| actor_type | Type of user logging in | ActorType.HUMAN_INTERNAL for employees, ActorType.HUMAN_CUSTOMER for customers |
| session_id | Session ID from your session management | req.session.id, cookies.get('session'), JWT jti |
| user_agent | HTTP request headers | req.headers['user-agent'] |
| user_role | User's role/permission level | Map your app's roles to UserRole constants |
| cloud_env_unique_id | AWS Account ID or GCP Project ID | Environment variable, hardcoded per environment |
| service_account_id | IAM role/service account running your code | Lambda execution role name, ECS task role, etc. |
⚠️ Tip: If you need to decode/extract values (e.g., from a JWT token), do this before calling the logging function.
📝 Step 3d: Implementation Example
Here's a complete example showing how to integrate security logging into a NextAuth.js signin flow:
import * as SecurityLogging from '@sunrun-security/sr-sec-ts-sns-logger';
import { decode } from 'jsonwebtoken';
// ===== STEP 1: Initialize ONCE at app startup (e.g., in _app.tsx or instrumentation.ts) =====
// Environment fields are set here and automatically included in ALL subsequent logs
SecurityLogging.initSecurityLogging({
testMode: process.env.NODE_ENV !== 'production',
cloudEnvType: SecurityLogging.CloudEnvType.PROD,
cloudEnvUniqueId: process.env.AWS_ACCOUNT_ID!,
cloudEnvName: process.env.ENVIRONMENT || 'production',
serviceAccountId: process.env.AWS_EXECUTION_ROLE_ARN!,
serviceName: 'customer-portal',
});
// ===== STEP 2: Use in your auth callbacks - much simpler! =====
// Example: NextAuth signin callback
async callbacks: {
async signIn({ user, account, profile }) {
try {
const userEmail = user.email;
const sessionId = account.access_token
? decode(account.access_token).jti
: `session-${Date.now()}`;
const userRole = profile.role === 'admin'
? SecurityLogging.UserRole.ADMIN
: SecurityLogging.UserRole.STANDARD_USER;
const actorType = userEmail.endsWith('@sunrun.com')
? SecurityLogging.ActorType.HUMAN_INTERNAL
: SecurityLogging.ActorType.HUMAN_CUSTOMER;
// 🔥 Fire-and-forget: Only pass event-specific fields!
// Environment fields (cloudEnvType, serviceName, etc.) are auto-included from init
SecurityLogging.fireAndForget(
SecurityLogging.logUserLogin({
// --- Actor/Session Context ---
actor_identifier: userEmail,
actor_type: actorType,
session_id: sessionId,
// --- Event-Specific Fields ---
event_type: SecurityLogging.EventType.LOGIN_ATTEMPT,
status: SecurityLogging.Status.SUCCESS,
user_agent: profile.user_agent || "unknown",
user_role: userRole,
auth_protocol: SecurityLogging.AuthProtocol.OAUTH2_JWT,
detail: SecurityLogging.Detail.USER_INITIATED,
}),
'login_attempt'
);
return true;
} catch (error) {
console.error('Security logging error:', error);
return true; // Never let logging errors break authentication
}
}
}🎯 Alternative Example: Express.js Login Route
import * as SecurityLogging from '@sunrun-security/sr-sec-ts-sns-logger';
// Initialize once at app startup (environment fields auto-included in all logs)
SecurityLogging.initSecurityLogging({
cloudEnvType: SecurityLogging.CloudEnvType.PROD,
cloudEnvUniqueId: process.env.AWS_ACCOUNT_ID!,
cloudEnvName: 'production',
serviceAccountId: 'ecs-task-role',
serviceName: 'api-server',
});
app.post('/api/login', async (req, res) => {
try {
const { email, password } = req.body;
const user = await authenticateUser(email, password);
if (!user) {
// Log failed login - only event-specific fields needed!
SecurityLogging.fireAndForget(
SecurityLogging.logUserLogin({
actor_identifier: email,
actor_type: SecurityLogging.ActorType.HUMAN_INTERNAL,
session_id: req.sessionID,
event_type: SecurityLogging.EventType.LOGIN_ATTEMPT,
status: SecurityLogging.Status.FAILURE,
user_agent: req.headers['user-agent'],
user_role: SecurityLogging.UserRole.UNKNOWN,
auth_protocol: SecurityLogging.AuthProtocol.FORM_BASED,
detail: SecurityLogging.Detail.INVALID_CREDENTIALS,
}),
'login_attempt'
);
return res.status(401).json({ error: 'Invalid credentials' });
}
// Log successful login
SecurityLogging.fireAndForget(
SecurityLogging.logUserLogin({
actor_identifier: user.email,
actor_type: SecurityLogging.ActorType.HUMAN_INTERNAL,
session_id: req.sessionID,
event_type: SecurityLogging.EventType.LOGIN_ATTEMPT,
status: SecurityLogging.Status.SUCCESS,
user_agent: req.headers['user-agent'],
user_role: user.role === 'admin'
? SecurityLogging.UserRole.ADMIN
: SecurityLogging.UserRole.STANDARD_USER,
auth_protocol: SecurityLogging.AuthProtocol.FORM_BASED,
detail: SecurityLogging.Detail.USER_INITIATED,
}),
'login_attempt'
);
res.json({ success: true, user });
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ error: 'Server error' });
}
});✅ Verify It Works
Run your application in development mode (with testMode: true) and trigger a login. You should see the security log printed to your console:
{
"timestamp": "2025-12-13T04:33:12.100224+00:00",
"event_type": "login_attempt",
"log_category": "authn_n_session",
"status": "status.general.success",
"event_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"actor_identifier": "[email protected]",
"actor_type": "actor.human.internal",
"session_id": "test-session-123",
"cloud_env_type": "prod",
"service_name": "test-app",
"cloud_env_unique_id": "123456789012",
"cloud_env_name": "production",
"service_account_id": "lambda-execution-role",
"user_agent": "Mozilla/5.0...",
"user_role": "role.classification.admin",
"auth_protocol": "auth.protocol.oauth2.jwt",
"detail": "detail.trigger.user_initiated",
"caller_function": "handleLogin",
"caller_file": "auth-controller.ts"
}Note: All fields are at the same level (flat structure) - there is no nested base_log or log_specifics object.
🎉 Congratulations! You've sent your first security log. The infrastructure is working. Now, let's move on to instrumenting your entire application.
🧠 The Developer's Mindset: Guidance and Principles
Implementing this framework requires more than just copying code. As an engineer, you possess the most intricate knowledge of your application. Adopting this standard effectively requires a thoughtful approach.
Think, Don't Just Apply: Use these templates as a guide, not a blind script. Question assumptions about your application's logic. For example, how does your service handle different user types or authentication workflows (e.g., OAuth)? Your implementation must reflect this context.
Know Your Endpoints: You are responsible for understanding the data sensitivity of your application's endpoints. Avoid mislabeling logs by correctly identifying the type and sensitivity of the data being accessed.
Security is Your Responsibility: Developers are the primary security implementers. This framework is a tool to help you build more secure applications by providing visibility into critical events.
📋 Prerequisites: Review Required
⚠️ MANDATORY: Before implementing security logging, you MUST review these resources:
🔗 Required Reading:
- Introduction to the Sunrun Security Logging Framework - Organizational standards
- security-log-fields.ts - Technical field definitions and constants
- security-logging-sns.ts - Function implementations and validation logic
Why This Review is Critical:
- 🎯 Standardized Values Required: All events MUST use predefined constants from
security-log-fields.ts - 📊 Schema Compliance: Understanding required fields prevents validation errors
- 🔍 Audit & Compliance: Consistent field values across all applications are mandatory
- 🛠️ Implementation Success: Saves development time by preventing common mistakes
- 🔬 Validation Logic: Review
security-logging-sns.tsto understand exactly how each function validates parameters
Key Areas to Review:
From security-log-fields.ts:
- Event Types: Supported security events (
EventTypeconstants) - Actor Types: Actor classifications (
ActorTypeconstants) - Status Values: Success/failure indicators (
Statusconstants) - Detail Values: Contextual information (
Detailconstants) - User Roles: Role classifications (
UserRoleconstants)
From security-logging-sns.ts:
- Function Signatures: Exact parameters for each of the 14 logging functions
- Validation Rules: What makes a log entry valid or invalid
- Required vs Optional Fields: Which fields are mandatory for each event type
- Error Messages: What validation errors look like and how to fix them
❌ Common Mistakes to Avoid:
- Using raw strings instead of predefined constants
- Skipping required fields for specific event types
- Creating custom detail values instead of using standardized constants
- Not importing constants properly from
security-log-fields.ts
Data Sensitivity Levels
When logging record access events, you must specify the sensitivity level of the data being accessed. Use this guide to select the appropriate level:
| Level | Constant | When to Use | Examples |
|-------|----------|-------------|----------|
| PUBLIC | DataSensitivityLevel.PUBLIC | Data that can be publicly shared | Product catalogs, marketing content, public APIs, help docs |
| NON_PUBLIC | DataSensitivityLevel.NON_PUBLIC | Internal business data, not customer-specific | Internal reports, aggregated metrics, config settings, team directories |
| CONFIDENTIAL | DataSensitivityLevel.CONFIDENTIAL | Customer/business data that shouldn't leak | Customer records, agreements, contracts, contact info, pricing |
| RESTRICTED | DataSensitivityLevel.RESTRICTED | Highly sensitive - PII, credentials, financial | SSN, bank accounts, passwords, API keys, health data |
Decision Guide:
Is this data publicly available (website, public API)?
→ YES: PUBLIC
→ NO: ↓
Does this data belong to a specific customer or contain PII?
→ YES: Is it highly sensitive (SSN, financial, credentials, health)?
→ YES: RESTRICTED
→ NO: CONFIDENTIAL
→ NO: NON_PUBLICThe same levels apply to EndpointSensitivity for API endpoint classification.
🔐 Request SNS Permissions
⚠️ IMPORTANT: Before implementing security logging, you must request SNS topic permissions.
Step 1: Review Requirements
Ensure you have:
- ✅ Read the Introduction to the Sunrun Security Logging Framework
- ✅ Reviewed
security-log-fields.ts - ✅ Identified which security events your application needs to log
- ✅ Understood your application's IAM role requirements
Step 2: Request SNS Topic Access
Before requesting, identify your IAM Role/Service Account based on your infrastructure:
Common Infrastructure Examples:
| Infrastructure Type | IAM Role/Service Account Example |
|---------------------|-----------------------------------|
| AWS Lambda | arn:aws:iam::123456789012:role/my-lambda-execution-role |
| AWS Amplify | arn:aws:iam::123456789012:role/amplify-my-app-role |
| AWS ECS/Fargate | arn:aws:iam::123456789012:role/ecsTaskExecutionRole |
| AWS EC2 | arn:aws:iam::123456789012:role/ec2-application-role |
| GKE (Google) | serviceAccount:[email protected] |
| AWS EKS | arn:aws:iam::123456789012:role/eks-pod-role |
| Local/Dev | Your IAM user: arn:aws:iam::123456789012:user/your-username |
💡 How to Find Your Role:
- Lambda: Check Lambda function configuration → Permissions → Execution role
- ECS/Fargate: Check task definition → Task execution IAM role
- EC2: Check instance → IAM role attached to instance
- EKS: Check pod service account or node IAM role
- Amplify: Check Amplify app → App settings → Service role
Post in #software-security Slack channel:
Hi Security Team! 👋
I need SNS permissions for security logging in my application.
📋 Application Details:
• Application/Service Name: [your-service-name]
• Environment(s): [prod/dev/staging]
• AWS Account ID: [123456789012] (where your application runs)
• IAM Role/Service Account: [see examples above - provide full ARN]
• SNS Topic Region: us-west-2 (fixed - this is where the security logging SNS topic is located)
📊 Event Types Needed:
• [List events: "User Login", "API Access", "Permission Changes"]
⏰ Timeline: [When you need this]
Thank you! 🙏Expected Response Time: 2-3 business days
📍 Note About Cross-Region Publishing: The security logging SNS topic is located in us-west-2, regardless of where your application runs. AWS SNS supports cross-region publishing, so applications running in any region (e.g., us-east-1, eu-west-1, ap-southeast-1) can publish to the us-west-2 topic without issues. Your IAM role just needs the appropriate SNS publish permissions.
Part 2: The Implementation Playbook
The first log is easy. The real work is identifying and mapping all security-relevant activities in your application. This playbook provides a structured process for achieving full coverage without getting overwhelmed.
Step 1: Identify Security-Relevant Activities
First, audit your application and create a simple list of all actions that should be logged. Don't worry about which logger function to use yet—just list the activities that occur.
Think About These Categories:
🔐 Authentication
- Where do users log in, log out, or fail to log in?
- Where do they handle MFA?
- Password reset/change flows?
🔑 Authorization
- Where are permissions, roles, or group memberships changed?
- Is there an admin panel?
- Can users be enabled/disabled?
- Impersonation features?
📊 Data Access
- Which API endpoints read or modify data?
- Which handle sensitive or customer data?
- Which access records (single or multiple)?
- Reporting and bulk export endpoints?
⚙️ Configuration Changes
- Where can users change their password, API keys, or MFA settings?
- Where can admins change system-wide settings like SSO?
- API key lifecycle management?
Step 2: Map Your Activities to Logger Events
Take your list of activities and map each one to one of the 14 available logger functions. Use this decision guide:
🤔 Decision Guide
Is the activity about a user logging in, out, or using MFA?
📖 Review the Authentication & Session functions →
- ➡️ Use:
logUserLogin()- User successfully/unsuccessfully logs in - ➡️ Use:
logMfaChallenge()- MFA verification attempt - ➡️ Use:
logUserLogout()- User logs out or session ends
Is the activity about changing what a user is allowed to do?
📖 Review the Authorization & Access functions →
- ➡️ Use:
logPermissionRoleChange()- User's role or permissions are modified - ➡️ Use:
logUserStatusChange()- User account enabled/disabled/deleted - ➡️ Use:
logImpersonationEvent()- Admin impersonates another user - ➡️ Use:
logUserInviteEvent()- User invitation sent/accepted
Is the activity an API endpoint being called?
📖 Review the API Endpoint & Data Access functions →
- ⚠️ Critical Decision Point: Does this endpoint access specific customer/entity data?
- YES - Use:
logRecordAccess()- Record(s) accessed (single or multiple - pass 1 or more IDs inid_list) - NO - It's a general API call (e.g.,
GET /api/statusor internal non-data endpoint)- ➡️ Use:
logApiRequest()- General API endpoint access
- ➡️ Use:
- YES - Use:
Is the activity about changing critical credentials or settings?
📖 Review the Key Configuration Changes functions →
- ➡️ Use:
logMfaStatusChange()- MFA enabled/disabled/device added - ➡️ Use:
logPasswordChangeReset()- Password changed or reset - ➡️ Use:
logApiKeyLifecycle()- API key created/revoked/modified - ➡️ Use:
logAuthMechanismModification()- SSO config or auth method changes
💡 Pro Tip: Click any function link above to see the exact code, parameters, and validation rules for that logging function.
Step 3: Track Your Implementation Progress
For any non-trivial application, you will have dozens of activities to log. To avoid getting lost, use a tracking template.
📊 Implementation Tracking Template
Use this Google Sheet template to track your progress:
Security Logging Implementation Tracker
This structured approach ensures you don't miss any critical events and provides clear visibility into the project's progress.
Part 3: API Function Reference
Now that you have a plan, use this section as a detailed reference for the parameters required by each function you identified in the playbook.
⚠️ IMPORTANT: Remember to pass the complete set of base fields with every single call. All fields are sent in a flat JSON structure (no nesting).
💡 TIP: For detailed implementation logic and validation rules, review the source code in security-logging-sns.ts. Each function includes comprehensive parameter validation and error handling.
Base Fields (Required for ALL Functions)
// Fields set ONCE in initSecurityLogging() - auto-included in all logs:
// cloudEnvType, cloudEnvUniqueId, cloudEnvName, serviceAccountId, serviceName
// Fields required in EACH logging call:
{
// Who did it
actor_identifier: string, // e.g., "[email protected]"
actor_type: ActorType, // e.g., ActorType.HUMAN_INTERNAL
// Session context
session_id: string, // e.g., "session-abc-123"
// Optional but recommended
source_ip_address?: string, // e.g., "192.168.1.100"
service_component_name?: string, // e.g., "auth-handler", "payment-processor"
}Authentication & Session Events
logUserLogin()
When to use: After a user attempts to sign in with their credentials.
Event Types:
EventType.LOGIN_ATTEMPT(consolidated event - usestatusfield to indicate success/failure)
Specific Parameters:
event_type(EventType): The type of login event (EventType.LOGIN_ATTEMPT)status(Status):Status.SUCCESSorStatus.FAILUREuser_agent(string): The user's browser user agentuser_role(UserRole): The role of the user logging inauth_protocol(AuthProtocol): Authentication method used (e.g.,AuthProtocol.FORM_BASED,AuthProtocol.OAUTH2_JWT,AuthProtocol.SAML)detail(Detail): Context for the logindevice_id(string, optional): Unique identifier for the user's device
Example (Successful Login - Fire-and-Forget):
// Recommended: Non-blocking, won't delay your response
// Note: Environment fields (cloudEnvType, serviceName, etc.) set in initSecurityLogging()
fireAndForget(
logUserLogin({
// Actor/Session context
actor_identifier: "[email protected]",
actor_type: ActorType.HUMAN_INTERNAL,
session_id: request.session.id,
source_ip_address: "192.168.1.100",
// Event-specific fields
event_type: EventType.LOGIN_ATTEMPT,
status: Status.SUCCESS,
user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
user_role: UserRole.ADMIN,
auth_protocol: AuthProtocol.OAUTH2_JWT,
detail: Detail.USER_INITIATED,
}),
'login_attempt'
);Login Failure Example:
fireAndForget(
logUserLogin({
// ... base log fields ...
event_type: EventType.LOGIN_ATTEMPT,
status: Status.FAILURE,
user_agent: request.headers['user-agent'],
user_role: UserRole.CUSTOMER_USER,
auth_protocol: AuthProtocol.FORM_BASED,
detail: Detail.INVALID_CREDENTIALS, // or Detail.ACCOUNT_LOCKED
}),
'login_attempt'
);logMfaChallenge()
When to use: When a user completes (or fails) an MFA challenge.
Event Type:
EventType.MFA_CHALLENGE
Specific Parameters:
event_type(EventType): Must beEventType.MFA_CHALLENGEstatus(Status):Status.SUCCESSorStatus.FAILUREmfa_type(MfaType): Type of MFA used (e.g.,MfaType.TOTP,MfaType.SMS)user_agent(string): Browser user agentuser_role(UserRole): User's roledetail(Detail): For success:Detail.USER_INITIATED; For failure:Detail.MFA_INVALID_CODE,Detail.MFA_EXPIRED_CODE,Detail.MFA_DEVICE_NOT_ENROLLED,Detail.MFA_TOO_MANY_ATTEMPTSdevice_id(string, optional): Device identifier
Example - MFA Failure:
// Link MFA to login via session_id
fireAndForget(
logMfaChallenge({
// ... base log fields ...
session_id: 'session-mfa-flow-123', // Same as login_attempt session_id
event_type: EventType.MFA_CHALLENGE,
status: Status.FAILURE, // MFA failed
mfa_type: MfaType.TOTP,
user_agent: request.headers['user-agent'],
user_role: UserRole.ADMIN,
detail: Detail.MFA_INVALID_CODE, // Specific failure reason
}),
'mfa_challenge'
);logUserLogout()
When to use: When a user logs out (voluntarily or forced).
Event Types: EventType.USER_LOGOUT
Specific Parameters:
event_type(EventType): Logout event typestatus(Status): Success or failureuser_agent(string): Browser user agentuser_role(UserRole): User's roledetail(Detail): Reason for logout (e.g.,Detail.USER_INITIATED,Detail.SESSION_TIMEOUT,Detail.ADMIN_INITIATED)device_id(string, optional): Device identifier
Example:
fireAndForget(
logUserLogout({
// ... base log fields ...
event_type: EventType.USER_LOGOUT,
status: Status.SUCCESS,
user_agent: request.headers['user-agent'],
user_role: UserRole.SALES_REP,
detail: Detail.SESSION_TIMEOUT,
}),
'user_logout'
);Authorization & Access Events
logPermissionRoleChange()
When to use: When an admin grants or revokes a role, permission, or group membership from a user.
Event Types:
EventType.ROLE_ASSIGN/EventType.ROLE_REVOKEEventType.PERMISSION_GRANT/EventType.PERMISSION_REVOKEEventType.GROUP_MEMBERSHIP_ADD/EventType.GROUP_MEMBERSHIP_REMOVE
Specific Parameters:
event_type(EventType): The change event typestatus(Status): Success or failuretarget_user_identifier(string): The user whose permissions are changingobject_changed(string): The type of object being modified (e.g., "role", "permission", "group")previous_value(string): The value before the changenew_value(string): The value after the changedetail(Detail, optional): Context for the change
Example:
// Note: Environment fields set in initSecurityLogging()
fireAndForget(
logPermissionRoleChange({
// Actor/Session context - actor is the admin making the change
actor_identifier: "[email protected]",
actor_type: ActorType.HUMAN_INTERNAL,
session_id: request.session.id,
// Event-specific fields
event_type: EventType.ROLE_ASSIGN,
status: Status.SUCCESS,
target_user_identifier: "[email protected]", // User being changed
object_changed: "role",
previous_value: "Sales Rep",
new_value: "Sales Manager",
detail: Detail.ADMIN_INITIATED,
}),
'role_assign'
);logUserStatusChange()
When to use: When a user account is enabled, disabled, locked, or unlocked.
Event Types: EventType.USER_STATUS_CHANGE
Specific Parameters:
event_type(EventType): Status change eventstatus(Status): Success or failuretarget_user_identifier(string): The user whose status is changingdetail(Detail): Type of status change (e.g.,Detail.USER_ENABLED,Detail.USER_DISABLED,Detail.ACCOUNT_LOCKED)
Example:
fireAndForget(
logUserStatusChange({
// ... base log fields ...
event_type: EventType.USER_STATUS_CHANGE,
status: Status.SUCCESS,
target_user_identifier: "[email protected]",
detail: Detail.USER_DISABLED,
}),
'user_status_change'
);logImpersonationEvent()
When to use: When a support admin impersonates a user (start or end).
Event Types: EventType.IMPERSONATION_EVENT
Specific Parameters:
event_type(EventType): Impersonation eventstatus(Status): Success or failuretarget_user_identifier(string): The user being impersonateddetail(Detail):Detail.IMPERSONATION_STARTorDetail.IMPERSONATION_END
Example:
fireAndForget(
logImpersonationEvent({
// Base log fields - actor is the support admin
actor_identifier: "[email protected]",
// ... other base fields ...
event_type: EventType.IMPERSONATION_EVENT,
status: Status.SUCCESS,
target_user_identifier: "[email protected]",
detail: Detail.IMPERSONATION_START,
}),
'impersonation_start'
);logUserInviteEvent()
When to use: When a new user is invited to the system.
Event Types: EventType.USER_INVITE_EVENT
Specific Parameters:
event_type(EventType): Invite eventstatus(Status): Success or failuretarget_user_email(string): Email of the invited userassigned_role(UserRole): Role being assigned to the new userinvite_status(InviteStatus): Status of the invite (e.g.,InviteStatus.SENT,InviteStatus.ACCEPTED)detail(Detail): Context for the invite
Example:
fireAndForget(
logUserInviteEvent({
// ... base log fields ...
event_type: EventType.USER_INVITE_EVENT,
status: Status.SUCCESS,
target_user_email: "[email protected]",
assigned_role: UserRole.SALES_REP,
invite_status: InviteStatus.SENT,
detail: Detail.USER_INITIATED,
}),
'user_invite'
);API & Data Access Events
logApiRequest()
When to use: For general API calls that do NOT access specific customer/entity data. Use this for system endpoints, health checks, or internal APIs.
⚠️ Important: If the endpoint accesses customer/entity data, use logRecordAccess() instead.
Event Types: EventType.API_REQUEST_PROCESSED
Specific Parameters:
event_type(EventType): API request eventstatus(Status): Success or failureauth_protocol(AuthProtocol): How the request was authenticated (e.g.,AuthProtocol.API_KEY,AuthProtocol.OAUTH2_JWT)endpoint_path(string): The API path that was calledhttp_method(HttpMethod): The HTTP method (e.g.,HttpMethod.GET,HttpMethod.POST)authorization_status(Status): Whether authorization succeededendpoint_sensitivity(EndpointSensitivity): Sensitivity level of the endpointdetail(Detail, optional): Additional context
Example:
fireAndForget(
logApiRequest({
// ... base log fields ...
event_type: EventType.API_REQUEST_PROCESSED,
status: Status.SUCCESS,
auth_protocol: AuthProtocol.API_KEY,
endpoint_path: "/api/v1/health",
http_method: HttpMethod.GET,
authorization_status: Status.SUCCESS,
endpoint_sensitivity: EndpointSensitivity.PUBLIC,
detail: Detail.NOT_APPLICABLE,
}),
'api_request'
);logRecordAccess()
When to use: For endpoints that access customer/entity records - whether a single record or multiple records. Use this unified function for all record access logging.
Event Types:
EventType.RECORD_ACCESS(unified event type for all record access)
Specific Parameters:
event_type(EventType):EventType.RECORD_ACCESSstatus(Status): Success or failureid_list(string[]): List of record IDs being accessed (can be a single ID or multiple)endpoint_path(string): The API path being accesseddata_sensitivity_level(DataSensitivityLevel): Sensitivity of the data accesseddetail(Detail): Context for the action (e.g.,Detail.VIEW_RECORD,Detail.EXPORT_REPORT)
Note: record_count is automatically calculated from id_list.length - you don't need to provide it.
Example (Single Record Access):
fireAndForget(
logRecordAccess({
// ... base log fields (or use auto-context extraction) ...
event_type: EventType.RECORD_ACCESS,
status: Status.SUCCESS,
id_list: ["cust_12345"], // Single record - just pass one ID in the list
endpoint_path: "/api/customers/cust_12345",
data_sensitivity_level: DataSensitivityLevel.CONFIDENTIAL, // Customer data
detail: Detail.VIEW_RECORD,
}),
'record_access'
);Example (Multi-Record Export):
fireAndForget(
logRecordAccess({
// ... base log fields (or use auto-context extraction) ...
event_type: EventType.RECORD_ACCESS,
status: Status.SUCCESS,
id_list: ["cust_001", "cust_002", "cust_003", /* ... */], // Multiple records
endpoint_path: "/api/customers/export",
data_sensitivity_level: DataSensitivityLevel.CONFIDENTIAL, // Customer data
detail: Detail.EXPORT_REPORT,
}),
'record_access'
);Large Record Lists (Automatic Batching): When accessing large numbers of records, the list may exceed SNS message size limits. The library automatically handles this by:
- Generating a unique
event_uuidfor the log event - Splitting the
id_listinto multiple messages - Adding
partandtotal_partsfields to each message for correlation
This is handled transparently - you just pass the full id_list and the library takes care of batching.
Key Configuration & Security Changes
logMfaStatusChange()
When to use: When MFA is enabled, disabled, or a device is added/removed.
Event Types: EventType.MFA_STATUS_CHANGE
Specific Parameters:
event_type(EventType): MFA status change eventstatus(Status): Success or failuretarget_object(string): The user whose MFA status is changingmfa_status(Status): New MFA statusmfa_id(string): MFA device identifierdetail(Detail): Type of change (e.g.,Detail.MFA_ENABLED,Detail.MFA_DISABLED)
Example:
fireAndForget(
logMfaStatusChange({
// ... base log fields ...
event_type: EventType.MFA_STATUS_CHANGE,
status: Status.SUCCESS,
target_object: "[email protected]",
mfa_status: Status.SUCCESS,
mfa_id: "mfa-device-123",
detail: Detail.MFA_ENABLED,
}),
'mfa_status_change'
);logPasswordChangeReset()
When to use: When a user changes or resets their password.
Event Types: EventType.PASSWORD_CHANGE_RESET
Specific Parameters:
event_type(EventType): Password eventstatus(Status): Success or failuretarget_object(string): The user whose password is changingchange_status(Status): Status of the changedetail(Detail): Type of change (e.g.,Detail.PASSWORD_CHANGE,Detail.PASSWORD_RESET)
Example:
fireAndForget(
logPasswordChangeReset({
// ... base log fields ...
event_type: EventType.PASSWORD_CHANGE_RESET,
status: Status.SUCCESS,
target_object: "[email protected]",
change_status: Status.SUCCESS,
detail: Detail.PASSWORD_CHANGE,
}),
'password_change'
);logApiKeyLifecycle()
When to use: When API keys are created, rotated, or revoked.
Event Types: EventType.API_KEY_LIFECYCLE
Specific Parameters:
event_type(EventType): API key eventstatus(Status): Success or failuretarget_object(string): Identifier of the API keykey_status(Status): Status of the keydetail(Detail): Type of change (e.g.,Detail.API_KEY_CREATED,Detail.API_KEY_REVOKED)
Example:
fireAndForget(
logApiKeyLifecycle({
// ... base log fields ...
event_type: EventType.API_KEY_LIFECYCLE,
status: Status.SUCCESS,
target_object: "api-key-prod-2024",
key_status: Status.SUCCESS,
detail: Detail.API_KEY_CREATED,
}),
'api_key_created'
);logAuthMechanismModification()
When to use: When system-wide authentication settings change (e.g., SSO configuration, local auth settings).
Event Types: EventType.AUTH_MECHANISM_MODIFICATION
Specific Parameters:
event_type(EventType): Auth mechanism eventstatus(Status): Success or failuretarget_object(string): The auth mechanism being modifiedauth_status(Status): Status of the auth mechanismdetail(Detail): Type of change (e.g.,Detail.SSO_CONFIG_CREATED,Detail.SSO_CONFIG_MODIFIED)
Example:
fireAndForget(
logAuthMechanismModification({
// ... base log fields ...
event_type: EventType.AUTH_MECHANISM_MODIFICATION,
status: Status.SUCCESS,
target_object: "okta-integration",
auth_status: Status.SUCCESS,
detail: Detail.SSO_CONFIG_CREATED,
}),
'sso_config_created'
);📖 Complete Working Example
For a comprehensive end-to-end example showing all 14 security event types with production-ready code, see example-publish.ts.
Run the example: npx ts-node example-publish.ts
Part 3.5: Auto-Context Extraction Helpers
The package includes helper functions that automatically extract common security context fields from your Next.js application, reducing boilerplate and ensuring consistency.
Available Context Helpers
import {
createSecurityContext, // Main helper: extracts all available fields
getMissingContextFields, // Check which fields couldn't be auto-extracted
diagnoseSecurityContext, // Get detailed info on missing fields + how to fix
warnMissingContextFields, // Log warnings for missing fields (dev helper)
} from '@sunrun-security/sr-sec-ts-sns-logger';Auto-Extracted Fields
| Field | Source (Priority Order) | Notes |
|-------|-------------------------|-------|
| actor_identifier | session.user.email | From next-auth session |
| user_agent | request.headers['user-agent'] | Browser/client user agent |
| source_ip_address | x-forwarded-for → x-real-ip → cf-connecting-ip | Client IP from proxy headers |
| endpoint_path | request.nextUrl.pathname → request.url (parsed) | Request path |
| http_method | request.method | GET, POST, etc. |
| session_id | session.id → sessionToken (hashed) → jti (hashed) → email+expires (hashed) | Stable throughout user session |
| cloud_env_type | CLOUD_ENV_TYPE → NEXT_PUBLIC_ENVIRONMENT_NAME → NODE_ENV → VERCEL_ENV → Lambda function name pattern | Auto-mapped to prod/stage/dev/test |
| cloud_env_name | CLOUD_ENV_NAME → NEXT_PUBLIC_ENVIRONMENT_NAME → VERCEL_ENV → NODE_ENV | Human-readable name (formatted) |
| cloud_env_unique_id | CLOUD_ENV_UNIQUE_ID → AWS_ACCOUNT_ID → ARN parsing from AWS_EXECUTION_ROLE_ARN, AWS_LAMBDA_FUNCTION_ARN | AWS Account ID (12-digit) |
| service_name | SERVICE_NAME → NEXT_PUBLIC_APP_NAME → AWS_LAMBDA_FUNCTION_NAME (cleaned) → npm_package_name | Application/service name |
| service_account_id | SERVICE_ACCOUNT_ID → AWS_EXECUTION_ROLE_ARN → AWS_ROLE_ARN → ROLE_ARN → IAM user pattern from AWS_ACCESS_KEY_ID | IAM role/user ARN |
Note: If you set cloudEnvType, cloudEnvUniqueId, cloudEnvName, serviceAccountId, or serviceName during initSecurityLogging(), those values take precedence and are automatically included in all logs.
Fields Requiring Manual Input
These fields must be set manually and cannot be auto-extracted:
| Field | Why Manual? |
|-------|-------------|
| actor_type | Business logic decision (HUMAN_INTERNAL, SYSTEM, etc.) |
| user_role | Application-specific role from your database/auth system |
| event_type | Specific to the action being performed |
| status | Outcome of the operation |
| Other event-specific fields | Depends on the event type |
Usage Example
Basic Usage
import { getServerSession } from 'next-auth';
import { NextRequest, NextResponse } from 'next/server';
import {
createSecurityContext,
warnMissingContextFields,
logRecordAccess,
fireAndForget,
EventType,
Status,
ActorType,
UserRole,
Category,
} from '@sunrun-security/sr-sec-ts-sns-logger';
export async function GET(request: NextRequest) {
const session = await getServerSession();
// Auto-extract what we can from request/session/environment
const context = createSecurityContext(request, session);
// Optional: warn in development if fields couldn't be extracted
if (process.env.NODE_ENV === 'development') {
warnMissingContextFields(context);
}
// Use the context - logging function will return error if required fields missing
const result = await logRecordAccess({
...context, // All auto-extracted fields
event_type: EventType.RECORD_ACCESS,
actor_type: ActorType.HUMAN_INTERNAL, // Manual: business logic
user_role: UserRole.ADMIN, // Manual: from your auth system
status: Status.SUCCESS,
category: Category.CUSTOMER_DATA_ACTIONS,
id_list: ['customer-123'],
});
// Check result - no exceptions thrown
if (result.status === 'failure') {
console.error('Security logging failed:', result.message);
// Message will list any missing required fields
}
fireAndForget(Promise.resolve(result), 'record_access');
return NextResponse.json({ success: true });
}Required Environment Variables
For full auto-extraction to work, configure these environment variables:
# Required for cloud_env_type and cloud_env_name
NEXT_PUBLIC_ENVIRONMENT_NAME=production # or staging, development, etc.
# Required for service_name (or uses npm_package_name)
SERVICE_NAME=my-nextjs-app
# Required for cloud_env_unique_id (if not in AWS Lambda)
AWS_ACCOUNT_ID=123456789012
# Required for service_account_id (if not in AWS Lambda)
SERVICE_ACCOUNT_ID=arn:aws:iam::123456789012:role/my-role
# OR
AWS_EXECUTION_ROLE_ARN=arn:aws:iam::123456789012:role/my-roleHandling Missing Fields
Auto-extraction is best-effort - if environment variables aren't set, the fields will be undefined. The logging functions handle this gracefully by returning a failure response (not throwing):
const result = await logRecordAccess({ ...context, /* other fields */ });
if (result.status === 'failure') {
// result.message will say "Missing required fields: cloud_env_type, service_name"
console.error(result.message);
}Diagnosing Missing Fields in Development
Use diagnoseSecurityContext() to get detailed information about what's missing and how to fix it:
import { createSecurityContext, diagnoseSecurityContext } from '@sunrun-security/sr-sec-ts-sns-logger';
const context = createSecurityContext(request, session);
const diagnostics = diagnoseSecurityContext(context);
if (diagnostics.hasMissing) {
console.log('Missing fields - set these environment variables:');
diagnostics.suggestions.forEach(({ field, envVar, description }) => {
console.log(` ${field}: ${envVar} (${description})`);
});
}
// Example output:
// Missing fields - set these environment variables:
// cloud_env_type: CLOUD_ENV_TYPE or NEXT_PUBLIC_ENVIRONMENT_NAME (Environment type)
// service_name: SERVICE_NAME (Application/service name)Part 4: Production Readiness & Configuration
Once you have implemented and tested your logging using the playbook, follow these steps to prepare for production deployment.
AWS IAM Permissions
SNS Publishing Permissions
Add to your application's IAM role:
{
"Effect": "Allow",
"Action": ["sns:Publish"],
"Resource": "arn:aws:sns:${var.aws_region}:YOUR_SECURITY_ACCOUNT_ID:sr-sec-logging-log-topic-${local.environment}"
}KMS Permissions (if SNS topic is encrypted)
{
"Effect": "Allow",
"Action": [
"kms:GenerateDataKey",
"kms:Decrypt"
],
"Resource": "arn:aws:kms:${var.aws_region}:YOUR_SECURITY_ACCOUNT_ID:key/*"
}CloudWatch Metrics Permissions
{
"Effect": "Allow",
"Action": ["cloudwatch:PutMetricData"],
"Resource": "*",
"Condition": {
"StringEquals": {
"cloudwatch:namespace": "SecurityLogging"
}
}
}REQUIRED: Failure Monitoring
⚠️ CRITICAL REQUIREMENT: All teams using this library MUST implement CloudWatch alarms to monitor for security logging failures.
Quick Implementation
Step 1: Install AWS CloudWatch SDK
npm install @aws-sdk/client-cloudwatchStep 2: Set Up CloudWatch Metrics Helper (One-Time Setup)
Create a reusable CloudWatch metrics helper in your application:
// File: lib/security-metrics.ts
import { CloudWatchClient, PutMetricDataCommand } from '@aws-sdk/client-cloudwatch';
export class SecurityLoggingMetrics {
private cloudwatch: CloudWatchClient;
private serviceName: string;
private namespace: string;
private environment: string;
constructor() {
this.cloudwatch = new CloudWatchClient({ region: process.env.AWS_REGION || 'us-west-2' });
this.serviceName = process.env.SERVICE_NAME || 'your-service-name';
this.environment = process.env.ENV_TYPE || 'unknown';
this.namespace = 'SecurityLogging'; // Standard namespace for all services
}
/**
* Report a security logging failure to CloudWatch
* This is fire-and-forget to avoid blocking on metrics too
*/
reportFailure(eventType?: string): void {
const dimensions = [
{ Name: 'ServiceName', Value: this.serviceName },
{ Name: 'Environment', Value: this.environment }
];
// Add event type dimension if provided
if (eventType) {
dimensions.push({ Name: 'EventType', Value: eventType });
}
const command = new PutMetricDataCommand({
Namespace: this.namespace,
MetricData: [{
MetricName: 'log_failure',
Value: 1.0,
Unit: 'Count',
Timestamp: new Date(),
Dimensions: dimensions
}]
});
// Fire-and-forget: Don't await to avoid blocking
this.cloudwatch.send(command).catch(error => {
// Log but don't throw - metrics failure shouldn't break the app
console.error('Failed to report security logging failure metric:', error);
});
}
}
// Export a singleton instance
export const securityMetrics = new SecurityLoggingMetrics();Step 3: Wire Up Error Handler at Application Startup
In your application's initialization code (e.g., app.ts, index.ts, server.ts):
// File: app.ts or index.ts
import * as SecurityLogging from '@sunrun-security/sr-sec-ts-sns-logger';
import { securityMetrics } from './lib/security-metrics';
// Initialize security logging
SecurityLogging.initSecurityLogging({
testMode: process.env.NODE_ENV !== 'production'
});
// Set up error handler to automatically emit CloudWatch metrics on failures
SecurityLogging.setSecurityLoggingErrorHandler((error, eventType) => {
console.error(`Security logging failed for ${eventType}:`, error);
securityMetrics.reportFailure(eventType);
});Step 4: Use Fire-and-Forget Everywhere
Now in your application code, use fireAndForget and failures automatically get logged + reported:
// File: controllers/auth.ts
import * as SecurityLogging from '@sunrun-security/sr-sec-ts-sns-logger';
async function handleUserLogin(user, request) {
// Fire-and-forget: Failures automatically emit CloudWatch metrics via the error handler
// Note: Environment fields (cloudEnvType, serviceName, etc.) already set in initSecurityLogging()
SecurityLogging.fireAndForget(
SecurityLogging.logUserLogin({
actor_identifier: user.email,
actor_type: SecurityLogging.ActorType.HUMAN_INTERNAL,
session_id: request.session.id,
event_type: SecurityLogging.EventType.LOGIN_ATTEMPT,
status: SecurityLogging.Status.SUCCESS,
user_agent: request.headers['user-agent'],
user_role: SecurityLogging.UserRole.ADMIN,
auth_protocol: SecurityLogging.AuthProtocol.OAUTH2_JWT,
detail: SecurityLogging.Detail.USER_INITIATED,
}),
SecurityLogging.EventType.LOGIN_ATTEMPT // For better CloudWatch dimensions
);
return { success: true };
}✅ That's it! Your application now:
- Doesn't block on security logging (fire-and-forget)
- Automatically logs errors to console
- Automatically emits CloudWatch metrics for monitoring
- Stays fast even if SNS is slow or down
Step 5: Create CloudWatch Alarms
Production (strict monitoring):
{
"AlarmName": "security-logging-failures-prod",
"ComparisonOperator": "GreaterThanOrEqualToThreshold",
"EvaluationPeriods": 1,
"MetricName": "log_failure",
"Namespace": "SecurityLogging",
"Period": 300,
"Statistic": "Sum",
"Threshold": 1,
"AlarmDescription": "SEV_1: Critical - Security logging failures detected"
}Reusable Terraform Module
Use our pre-built Terraform module: security-logging-alerts
module "security_logging_alerts" {
source = "../../../templates/shared/security-logging-alerts"
service_name = "your-service-name"
environment = local.environment
cloudwatch_namespace = "YourTeam/SecurityLogging"
failure_threshold_immediate = 1
severity_immediate = "sev_1"
common_tags = local.common_tags
}Configuration Options
initSecurityLogging() Parameters
initSecurityLogging({
// --- SNS Configuration ---
topicArn?: string, // Override SNS topic ARN (auto-detected by default)
regionName?: string, // Override AWS region (defaults to us-west-2)
testMode?: boolean, // Enable test mode (logs to console, no SNS)
// --- Environment Configuration (set once, auto-included in all logs) ---
cloudEnvType?: string, // e.g., CloudEnvType.PROD, CloudEnvType.DEV
cloudEnvUniqueId?: string, // AWS Account ID, e.g., "123456789012"
cloudEnvName?: string, // Environment name, e.g., "production"
serviceAccountId?: string, // IAM role ARN or service account ID
serviceName?: string, // Your application name
});Production Example (recommended):
SecurityLogging.initSecurityLogging({
cloudEnvType: SecurityLogging.CloudEnvType.PROD,
cloudEnvUniqueId: process.env.AWS_ACCOUNT_ID!,
cloudEnvName: 'production',
serviceAccountId: process.env.AWS_EXECUTION_ROLE_ARN!,
serviceName: 'my-application',
});
// Now all log calls automatically include these environment fields!Development Example:
SecurityLogging.initSecurityLogging({
testMode: true, // Logs to console instead of SNS
cloudEnvType: SecurityLogging.CloudEnvType.DEV,
cloudEnvUniqueId: '687126124183',
cloudEnvName: 'development',
serviceAccountId: 'local-dev',
serviceName: 'my-application',
});Environment Variables (Alternative to Init Parameters)
| Variable | Description | Example |
|----------|-------------|---------|
| SECURITY_LOGS_TOPIC_ARN | Override SNS topic ARN | arn:aws:sns:us-west-2:123:topic |
| AWS_REGION | Override AWS region | us-west-2 |
| CLOUD_ENV_TYPE | Environment type | prod, dev, stage |
| CLOUD_ENV_UNIQUE_ID | AWS Account ID | 123456789012 |
| CLOUD_ENV_NAME | Environment name | production |
| SERVICE_ACCOUNT_ID | IAM role ARN | arn:aws:iam::123:role/my-role |
| SERVICE_NAME | Your service name | my-app |
Note: Init parameters take precedence over environment variables.
Appendix
Appendix A: Schema Structure & Fields
This module implements a flat JSON schema where all fields are at the same level (no nested objects).
Base Fields (In Every Log Entry)
These fields appear in every log entry. Most are set once during initialization:
| Field Name | Description | Example | Set Via |
|------------|-------------|---------|---------|
| timestamp | Event timestamp in UTC | "2025-12-13T04:33:12.000Z" | Auto-generated |
| event_uuid | Unique event identifier | "1ade00c0-3dde-4d1f-..." | Auto-generated |
| log_category | High-level event category | "authn_n_session" | Auto-derived from event_type |
| caller_function | Function that called the logger | "signIn" | Auto-extracted |
| caller_file | File that called the logger | "auth.ts" | Auto-extracted |
| cloud_env_type | Environment type | "prod", "dev" | Init or per-call |
| service_name | Application/service name | "my-app" | Init or per-call |
| cloud_env_unique_id | AWS Account ID | "123456789012" | Init or per-call |
| cloud_env_name | Environment name | "production" | Init or per-call |
| service_account_id | IAM role ARN | "arn:aws:iam::..." | Init or per-call |
| event_type | Event type identifier | "login_attempt" | Per-call (required) |
| status | Event outcome | "status.general.success" | Per-call (required) |
| actor_identifier | User email/ID | "[email protected]" | Per-call (required) |
| actor_type | Actor classification | "actor.human.internal" | Per-call (required) |
| session_id | Session identifier | "session-abc-123" | Per-call (required) |
| source_ip_address | Source IP address | "203.0.113.54" | Per-call (optional) |
| service_component_name | Specific component within service | "auth-handler" | Per-call (optional) |
Key Point: Set cloud_env_*, service_* fields once in initSecurityLogging() - they're automatically included in all subsequent logs!
Event-Specific Fields (Additional Required Fields)
Each logging function requires additional fields beyond the base fields. These event-specific fields are included in the same flat JSON structure (not nested). See the API Function Reference for details on each function's required fields.
Example Combined Structure:
{
"timestamp": "2025-12-13T04:33:35.331000+00:00",
"event_type": "login_attempt",
"log_category": "authn_n_session",
"status": "status.general.success",
"actor_identifier": "[email protected]",
"actor_type": "actor.human.internal",
"session_id": "session-abc-123",
"cloud_env_type": "prod",
"service_name": "my-application",
"cloud_env_unique_id": "123456789012",
"cloud_env_name": "production",
"service_account_id": "arn:aws:iam::123456789012:role/my-role",
"source_ip_address": "10.0.0.1",
"user_agent": "Mozilla/5.0 (Test Browser)",
"user_role": "role.classification.admin",
"auth_protocol": "auth.protocol.oauth2_j