Shiprocket is transforming the logistics landscape for thousands of MSMEs, but our commitment goes beyond logistics. With more than 2,50,000+ merchants onboard, we are dedicated to providing high standards in web application security. The trust that thousands of businesses have in Shiprocket constantly pushes us to innovate and strengthen our digital defenses, ensuring our systems remain resilient against emerging threats. All of this is just to manage sensitive customer and order information.
From preventing data breaches to maintaining the integrity of user sessions, ensuring the safety of our digital infrastructure is a responsibility we take very seriously. Monitoring and proactively addressing all potential vulnerabilities is how we combat the increasing frequency of cyberattacks or slowdowns that can hamper experience. Web application security is non-negotiable for us and we take all necessary measures to track and curb them down.
Fortifying Web Security: How Shiprocket Detects Developer Tools
In the ever-evolving landscape of web development, ensuring the security of sensitive information is paramount for us. One of the critical aspects of Shiprocket’s web application security is monitoring user interactions, particularly those involving browser developer tools. Detecting when these tools are open can help prevent unauthorised access to sensitive data, maintain the integrity of user sessions, and enforce security protocols.
Introduction to DevTools Detection
Before we dive into the code, let’s outline why detecting developer tools is essential. In a web application, detecting when developer tools are open can help protect sensitive information and ensure users adhere to security policies.
Setting Up the DevTools Detection Service
This section introduces the DevToolsDetectionService which performs the detection of developer tools usage.
Code Breakdown:
import { Injectable, Renderer2, RendererFactory2 } from ‘@angular/core’; import { Router } from ‘@angular/router’; import { interval } from ‘rxjs’; import { switchMap } from ‘rxjs/operators’; @Injectable({ providedIn: ‘root’ }) export class DevToolsDetectionService { private threshold = 160; // Threshold to detect DevTools open private isDevToolsOpen = false; private originalContent: string | null = null; private lastCheckTime = 0; private initialDevicePixelRatio = window.devicePixelRatio; private previousRoute: string | null = null; private renderer: Renderer2; constructor( private router: Router, rendererFactory: RendererFactory2 ) { this.renderer = rendererFactory.createRenderer(null, null); this.startDetection(); this.disableContextMenuAndCopy(); } |
- Service Setup: DevToolsDetectionService is created as a singleton service (providedIn: ‘root’).
- Variables: Defines necessary variables like threshold, isDevToolsOpen, and initialDevicePixelRatio.
Checking Developer Tools Status:
private checkDevToolsStatus() { const now = performance.now(); if (now – this.lastCheckTime > 1000) { // Check every second this.lastCheckTime = now; const zoomed = Math.abs(window.devicePixelRatio – this.initialDevicePixelRatio) > 0.1; if (zoomed) { this.showMessage(‘Developer Tools appear to be open or screen settings have changed. Please close Developer Tools and adjust your screen settings.’); return; } const devToolsOpen = window.outerWidth – window.innerWidth > this.threshold || window.outerHeight – window.innerHeight > this.threshold; if (devToolsOpen && !this.isDevToolsOpen) { this.isDevToolsOpen = true; this.handleDevToolsOpen(); } else if (!devToolsOpen && this.isDevToolsOpen) { this.isDevToolsOpen = false; this.handleDevToolsClose(); } } } |
- Timing and Detection: Checks if the developer tools are open by comparing the outer and inner dimensions of the window and detecting significant changes in the device pixel ratio.
Handling Open and Close Events:
private handleDevToolsOpen() { this.previousRoute = this.router.url; this.router.navigate([‘/not-authorized’]); this.hideContent(); this.showMessage(‘Developer Tools are open. You are being redirected for security reasons.’); } private handleDevToolsClose() { if (this.previousRoute) { setTimeout(() => { window.location.href = this.previousRoute; }, 100); // Delay to allow browser state to settle } else { window.location.reload(); } } |
- Open: Redirects to a /not-authorized route and hides content.
- Close: Navigates back to the previous route or reloads the page.
Displaying and Hiding Messages:
private showMessage(message: string) { const messageElement = document.createElement(‘div’); messageElement.className = ‘devtools-message’; messageElement.textContent = message; Object.assign(messageElement.style, { position: ‘fixed’, top: ‘0’, left: ‘0’, right: ‘0’, backgroundColor: ‘rgba(255, 0, 0, 0.8)’, color: ‘#fff’, textAlign: ‘center’, padding: ’10px’, zIndex: ‘9999’ }); this.renderer.appendChild(document.body, messageElement); } private hideContent() { if (!this.originalContent) { this.originalContent = document.body.innerHTML; } document.body.innerHTML = ”; // Clear the content } |
- Message: Creates and styles a message element that is displayed to users when developer tools are detected.
- Hide Content: Clears the content of the page to prevent tampering.
Disabling Context Menu and Copy:
private disableContextMenuAndCopy() { this.renderer.listen(‘document’, ‘contextmenu’, (event) => { event.preventDefault(); return false; }); this.renderer.listen(‘document’, ‘copy’, (event) => { event.preventDefault(); return false; }); } |
- Context Menu: Disables right-click context menu.
- Copy: Disables the copy functionality.
Starting the Detection Process:
private startDetection() { interval(1000).subscribe(() => this.checkDevToolsStatus()); } } |
- Interval: Sets up a periodic check every second to monitor for developer tools.
Integrating DevTools Detection in Your Angular Application
Here’s how you would integrate this service into your Angular app:
App Component Example:
import { Component } from ‘@angular/core’; import { DevToolsDetectionService } from ‘./shared/services/dev-tools-detection.service’; @Component({ selector: ‘app-root’, templateUrl: ‘./app.component.html’, styleUrls: [‘./app.component.scss’], }) export class AppComponent { constructor( private devToolsDetectionService: DevToolsDetectionService ) {} } |
- Integration: Simply inject the DevToolsDetectionService into your main AppComponent to activate the detection.
Testing and Validation
Manual Testing Tips:
- Open Developer Tools and observe if the detection and redirection work as expected.
- Ensure the message is displayed and content is hidden appropriately.
Considerations and Limitations
- False Positives: Adjust the threshold and pixel ratio checks to minimize false positives.
- User Experience: Make sure that detection does not disrupt normal user behavior or create a poor user experience.
Conclusion
At Shiprocket, securing our platform is a continuous journey that helps us ensure data security. By incorporating developer tools detection we have enhanced security and ensured that users follow necessary protocols. Through this blog, we’ve shared our approach, from setting up the detection service to integrating it seamlessly into the Angular app. Customising this approach to fit our application’s needs will always be crucial for us.
Check full code:
import { Injectable, Renderer2, RendererFactory2 } from ‘@angular/core’; import { Router } from ‘@angular/router’; import { BehaviorSubject, interval } from ‘rxjs’; import { switchMap } from ‘rxjs/operators’; @Injectable({ providedIn: ‘root’ }) export class DevToolsDetectionService { private threshold = 160; // Threshold to detect DevTools open private isDevToolsOpen = false; private originalContent: string | null = null; private lastCheckTime = 0; private initialDevicePixelRatio = window.devicePixelRatio; private previousRoute: string | null = null; private renderer: Renderer2; constructor( private router: Router, rendererFactory: RendererFactory2 ) { this.renderer = rendererFactory.createRenderer(null, null); this.startDetection(); this.disableContextMenuAndCopy(); } private shouldActivateDevToolsDetection(): boolean { // Replace with actual user check logic const user = { is_admin: false, is_seller: false }; // Replace with actual logic return !user.is_admin && !user.is_seller; } private checkDevToolsStatus() { if (!this.shouldActivateDevToolsDetection()) { return; } const now = performance.now(); if (now – this.lastCheckTime > 1000) { // Check every second this.lastCheckTime = now; // Check if device pixel ratio has changed significantly, indicating a zoom level change const zoomed = Math.abs(window.devicePixelRatio – this.initialDevicePixelRatio) > 0.1; if (zoomed) { this.showMessage(‘Developer Tools appear to be open or screen settings have changed. For security and functionality, please close Developer Tools and adjust your screen settings to use this website.’); return; } const devToolsOpen = window.outerWidth – window.innerWidth > this.threshold || window.outerHeight – window.innerHeight > this.threshold; if (devToolsOpen && !this.isDevToolsOpen) { this.isDevToolsOpen = true; console.log(‘Warning: Developer Tools are open.’); this.handleDevToolsOpen(); } else if (!devToolsOpen && this.isDevToolsOpen) { this.isDevToolsOpen = false; console.log(‘Developer Tools are closed.’); this.handleDevToolsClose(); } } } private handleDevToolsOpen() { // Save the current route before redirecting this.previousRoute = this.router.url; this.router.navigate([‘/not-authorized’]); this.hideContent(); this.showMessage(‘Developer Tools are open. You are being redirected for security reasons.’); } private handleDevToolsClose() { if (this.previousRoute) { // Use window.location.href to navigate to the previous route setTimeout(() => { window.location.href = this.previousRoute; }, 100); // Delay to allow browser state to settle } else { // Refresh the page if no previous route is saved window.location.reload(); } } private hideContent() { if (!this.originalContent) { this.originalContent = document.body.innerHTML; } document.body.innerHTML = ”; // Clear the content } private showContent() { if (this.originalContent) { document.body.innerHTML = this.originalContent; this.originalContent = null; // Clear the saved content } } private showMessage(message: string) { const messageElement = document.createElement(‘div’); messageElement.className = ‘devtools-message’; messageElement.textContent = message; Object.assign(messageElement.style, { position: ‘fixed’, top: ‘0’, left: ‘0’, right: ‘0’, backgroundColor: ‘rgba(255, 0, 0, 0.8)’, color: ‘#fff’, textAlign: ‘center’, padding: ’10px’, zIndex: ‘9999’ }); this.renderer.appendChild(document.body, messageElement); } private removeMessage() { const messageElement = document.querySelector(‘.devtools-message’); if (messageElement) { this.renderer.removeChild(document.body, messageElement); } } private disableContextMenuAndCopy() { this.renderer.listen(‘document’, ‘contextmenu’, (event) => { event.preventDefault(); // Disable right-click context menu return false; }); this.renderer.listen(‘document’, ‘copy’, (event) => { event.preventDefault(); // Disable copy command return false; }); } private startDetection() { if (this.shouldActivateDevToolsDetection()) { interval(1000) .pipe( switchMap(() => { this.checkDevToolsStatus(); return []; }) ) .subscribe(); } } } |