Phased Migration Strategy
Phased Migration Strategy
Migrating legacy applications requires a carefully phased approach that minimizes risk while progressively improving security:
// Phased CSP Migration Manager
class PhasedCSPMigration {
constructor(application) {
this.application = application;
this.currentPhase = 0;
this.phases = this.defineMigrationPhases();
}
defineMigrationPhases() {
return [
{
name: 'Phase 0: Preparation',
duration: '2-4 weeks',
objectives: [
'Complete application assessment',
'Set up monitoring infrastructure',
'Train development team',
'Create rollback procedures'
],
implementation: this.implementPhase0(),
successCriteria: {
monitoringSetup: true,
teamTraining: 100,
rollbackTested: true
}
},
{
name: 'Phase 1: Report-Only Deployment',
duration: '4-6 weeks',
objectives: [
'Deploy permissive report-only policy',
'Collect comprehensive violation data',
'Identify all resource dependencies',
'Build initial allowlist'
],
implementation: this.implementPhase1(),
successCriteria: {
coverageDays: 30,
violationsAnalyzed: true,
falsePositivesResolved: 95
}
},
{
name: 'Phase 2: Code Refactoring',
duration: '8-12 weeks',
objectives: [
'Refactor inline event handlers',
'Externalize inline scripts',
'Implement nonce system',
'Update third-party integrations'
],
implementation: this.implementPhase2(),
successCriteria: {
inlineHandlersRemoved: 100,
inlineScriptsReduced: 90,
nonceSystemOperational: true
}
},
{
name: 'Phase 3: Gradual Enforcement',
duration: '4-6 weeks',
objectives: [
'Enable enforcement for subset of users',
'Monitor for functionality issues',
'Refine policy based on feedback',
'Expand enforcement gradually'
],
implementation: this.implementPhase3(),
successCriteria: {
enforcementPercentage: 100,
userImpactAcceptable: true,
performanceImpactMinimal: true
}
},
{
name: 'Phase 4: Optimization',
duration: 'Ongoing',
objectives: [
'Tighten security policies',
'Optimize performance',
'Automate policy management',
'Establish maintenance procedures'
],
implementation: this.implementPhase4(),
successCriteria: {
securityPostureImproved: true,
automationImplemented: true,
maintenanceProcessEstablished: true
}
}
];
}
implementPhase0() {
return {
monitoring: `
// Set up comprehensive CSP monitoring
class CSPMonitoringSetup {
constructor() {
this.endpoints = {
reportUri: '/api/csp-reports',
analytics: '/api/csp-analytics',
alerts: '/api/csp-alerts'
};
}
setupReportingEndpoint() {
app.post(this.endpoints.reportUri, (req, res) => {
const report = req.body['csp-report'];
// Store in database
db.cspReports.insert({
timestamp: new Date(),
violation: report,
userAgent: req.headers['user-agent'],
sessionId: req.session?.id
});
// Real-time analysis
this.analyzeViolation(report);
res.status(204).end();
});
}
setupDashboard() {
return {
metrics: [
'Total violations per hour',
'Unique violation sources',
'Violation by directive',
'User impact assessment'
],
alerts: [
'Spike in violations',
'New violation source',
'Critical functionality blocked'
]
};
}
}
`,
training: {
developers: [
'CSP fundamentals workshop',
'Legacy code refactoring techniques',
'CSP debugging tools training'
],
qa: [
'CSP testing procedures',
'Violation identification',
'Regression testing with CSP'
],
support: [
'Common CSP issues',
'User complaint handling',
'Escalation procedures'
]
}
};
}
implementPhase1() {
return {
initialPolicy: `
// Permissive report-only policy for discovery
Content-Security-Policy-Report-Only:
default-src * 'unsafe-inline' 'unsafe-eval' data: blob:;
report-uri /api/csp-reports;
`,
violationAnalyzer: `
class ViolationAnalyzer {
constructor() {
this.violations = new Map();
this.patterns = new Map();
}
analyzeViolations(reports) {
const analysis = {
byDirective: {},
bySource: {},
byPage: {},
recommendations: []
};
reports.forEach(report => {
const violation = report['csp-report'];
const directive = violation['violated-directive'];
const source = violation['blocked-uri'];
// Group by directive
if (!analysis.byDirective[directive]) {
analysis.byDirective[directive] = {
count: 0,
sources: new Set()
};
}
analysis.byDirective[directive].count++;
analysis.byDirective[directive].sources.add(source);
// Identify patterns
if (source.includes('google-analytics')) {
analysis.recommendations.push({
action: 'Add Google Analytics to CSP',
directives: ['script-src', 'img-src', 'connect-src'],
sources: ['https://www.google-analytics.com']
});
}
});
return analysis;
}
}
`
};
}
implementPhase2() {
return {
refactoringTools: `
// Automated refactoring helpers
class LegacyCodeRefactorer {
refactorInlineHandlers(html) {
const $ = cheerio.load(html);
const handlers = [];
// Extract and remove inline handlers
$('[onclick], [onload], [onchange]').each((i, elem) => {
const $elem = $(elem);
const events = {};
['onclick', 'onload', 'onchange'].forEach(attr => {
if ($elem.attr(attr)) {
events[attr] = $elem.attr(attr);
$elem.removeAttr(attr);
}
});
if (Object.keys(events).length > 0) {
const id = $elem.attr('id') || \`legacy-elem-\${i}\`;
$elem.attr('id', id);
handlers.push({ id, events });
}
});
// Generate external script
const script = this.generateEventScript(handlers);
return {
html: $.html(),
script: script
};
}
generateEventScript(handlers) {
let script = 'document.addEventListener("DOMContentLoaded", function() {\\n';
handlers.forEach(({ id, events }) => {
Object.entries(events).forEach(([event, handler]) => {
const eventType = event.substring(2); // Remove 'on' prefix
script += \` document.getElementById('\${id}').addEventListener('\${eventType}', function(event) {\\n\`;
script += \` \${handler}\\n\`;
script += \` });\\n\`;
});
});
script += '});';
return script;
}
}
`,
nonceImplementation: `
// Nonce system for legacy applications
class LegacyNonceSystem {
constructor() {
this.nonceCache = new Map();
}
middleware() {
return (req, res, next) => {
const nonce = crypto.randomBytes(16).toString('base64');
res.locals.nonce = nonce;
// Override render for legacy templating engines
this.overrideRender(res, nonce);
// Set CSP header with nonce
const policy = this.generateNoncePolicy(nonce);
res.setHeader('Content-Security-Policy-Report-Only', policy);
next();
};
}
overrideRender(res, nonce) {
const originalRender = res.render;
res.render = function(view, options, callback) {
options = options || {};
options.cspNonce = nonce;
// Add nonce injection helper
options.injectNonce = (html) => {
return html
.replace(/<script(?![^>]*nonce)/gi, \`<script nonce="\${nonce}"\`)
.replace(/<style(?![^>]*nonce)/gi, \`<style nonce="\${nonce}"\`);
};
originalRender.call(this, view, options, callback);
};
}
}
`
};
}
}