Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 81 additions & 22 deletions frontend/src/components/report/LayerModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,52 @@ import { CheckCircle, AlertCircle, Info } from 'lucide-react';
import './LayerModal.scss';

const FACTOR_HUMAN = {
SAST: { label: 'Code Safety', category: 'code', desc: 'Scans source code for known vulnerability patterns' },
VirusTotal: { label: 'Malware Scan', category: 'threat', desc: 'Checks against 70+ antivirus engines for malicious code' },
Obfuscation: { label: 'Hidden Code', category: 'code', desc: 'Detects deliberately obscured or unreadable code' },
Manifest: { label: 'Extension Config', category: 'code', desc: 'Validates security settings in the extension manifest' },
ChromeStats: { label: 'Threat Intel', category: 'threat', desc: 'Cross-references known threat databases' },
Webstore: { label: 'Store Reputation', category: 'trust', desc: 'Chrome Web Store ratings and user reviews' },
Maintenance: { label: 'Update Freshness', category: 'trust', desc: 'How recently the extension was updated by its developer' },
PermissionsBaseline: { label: 'Permission Risk', category: 'access', desc: 'Evaluates the sensitivity of requested browser permissions' },
PermissionCombos: { label: 'Dangerous Combos', category: 'access', desc: 'Flags risky combinations of permissions that enable data theft' },
NetworkExfil: { label: 'Data Sharing', category: 'data', desc: 'Detects if data is sent to external servers' },
CaptureSignals: { label: 'Screen Capture', category: 'data', desc: 'Checks for screen or tab recording capabilities' },
ToSViolations: { label: 'Policy Violations', category: 'policy', desc: 'Checks compliance with Chrome Web Store policies' },
Consistency: { label: 'Behavior Match', category: 'policy', desc: 'Compares stated purpose vs actual behavior' },
DisclosureAlignment: { label: 'Disclosure Accuracy', category: 'policy', desc: 'Validates privacy policy against actual data collection' },
SAST: { label: 'Code Safety', category: 'code', desc: 'Scans source code for known vulnerability patterns' },
VirusTotal: { label: 'Malware Scan', category: 'threat', desc: 'Checks against 70+ antivirus engines for malicious code' },
Obfuscation: { label: 'Hidden Code', category: 'code', desc: 'Detects deliberately obscured or unreadable code' },
Manifest: { label: 'Extension Config', category: 'code', desc: 'Validates security settings in the extension manifest' },
ChromeStats: { label: 'Threat Intel', category: 'threat', desc: 'Cross-references known threat databases' },
Webstore: { label: 'Store Reputation', category: 'trust', desc: 'Chrome Web Store ratings and user reviews' },
Maintenance: { label: 'Update Freshness', category: 'trust', desc: 'How recently the extension was updated by its developer' },
PermissionsBaseline: { label: 'Permission Risk', category: 'access', desc: 'Evaluates the sensitivity of requested browser permissions' },
PermissionCombos: { label: 'Dangerous Combos', category: 'access', desc: 'Flags risky combinations of permissions that enable data theft' },
NetworkExfil: { label: 'Data Sharing', category: 'data', desc: 'Detects if data is sent to external servers' },
CaptureSignals: { label: 'Screen Capture', category: 'data', desc: 'Checks for screen or tab recording capabilities' },
ToSViolations: { label: 'Policy Violations', category: 'policy', desc: 'Checks compliance with Chrome Web Store policies' },
Consistency: { label: 'Behavior Match', category: 'policy', desc: 'Compares stated purpose vs actual behavior' },
DisclosureAlignment: { label: 'Disclosure Accuracy', category: 'policy', desc: 'Validates privacy policy against actual data collection' },
};

const PERMISSION_RISKS = {
tabs: 'Can read browsing activity',
webRequest: 'Can intercept and modify traffic (high risk)',
webRequestBlocking: 'Can block and modify network requests (high risk)',
cookies: 'Can read and modify site cookies',
history: 'Can read full browsing history',
clipboardRead: 'Can read copied text from clipboard',
clipboardWrite: 'Can modify clipboard contents',
desktopCapture: 'Can record your screen',
tabCapture: 'Can record browser tabs',
nativeMessaging: 'Can communicate with desktop apps',
proxy: 'Can route all traffic through external servers',
debugger: 'Can bypass security and monitor page internals',
management: 'Can disable or uninstall other extensions',
geolocation: 'Can access physical location',
bookmarks: 'Can read and modify bookmarks',
'<all_urls>': 'Can access data on all websites you visit',
'*://*/*': 'Can access data on all websites you visit',
'http://*/*': 'Can access data on all HTTP websites',
'https://*/*': 'Can access data on all HTTPS websites',
activeTab: 'Can access the current active tab',
storage: 'Can store data locally',
};
Comment on lines +28 to 50
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add missing high-risk explanation for browsingData.

high_risk_permissions can include browsingData (from the backend risk set), but this map has no specific entry for it, so users get the generic fallback instead of a plain-English risk explanation.

Proposed patch
 const PERMISSION_RISKS = {
   tabs: 'Can read browsing activity',
   webRequest: 'Can intercept and modify traffic (high risk)',
   webRequestBlocking: 'Can block and modify network requests (high risk)',
   cookies: 'Can read and modify site cookies',
+  browsingData: 'Can clear browsing data (history, cache, cookies) and hide traces of activity',
   history: 'Can read full browsing history',
   clipboardRead: 'Can read copied text from clipboard',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const PERMISSION_RISKS = {
tabs: 'Can read browsing activity',
webRequest: 'Can intercept and modify traffic (high risk)',
webRequestBlocking: 'Can block and modify network requests (high risk)',
cookies: 'Can read and modify site cookies',
history: 'Can read full browsing history',
clipboardRead: 'Can read copied text from clipboard',
clipboardWrite: 'Can modify clipboard contents',
desktopCapture: 'Can record your screen',
tabCapture: 'Can record browser tabs',
nativeMessaging: 'Can communicate with desktop apps',
proxy: 'Can route all traffic through external servers',
debugger: 'Can bypass security and monitor page internals',
management: 'Can disable or uninstall other extensions',
geolocation: 'Can access physical location',
bookmarks: 'Can read and modify bookmarks',
'<all_urls>': 'Can access data on all websites you visit',
'*://*/*': 'Can access data on all websites you visit',
'http://*/*': 'Can access data on all HTTP websites',
'https://*/*': 'Can access data on all HTTPS websites',
activeTab: 'Can access the current active tab',
storage: 'Can store data locally',
};
const PERMISSION_RISKS = {
tabs: 'Can read browsing activity',
webRequest: 'Can intercept and modify traffic (high risk)',
webRequestBlocking: 'Can block and modify network requests (high risk)',
cookies: 'Can read and modify site cookies',
browsingData: 'Can clear browsing data (history, cache, cookies) and hide traces of activity',
history: 'Can read full browsing history',
clipboardRead: 'Can read copied text from clipboard',
clipboardWrite: 'Can modify clipboard contents',
desktopCapture: 'Can record your screen',
tabCapture: 'Can record browser tabs',
nativeMessaging: 'Can communicate with desktop apps',
proxy: 'Can route all traffic through external servers',
debugger: 'Can bypass security and monitor page internals',
management: 'Can disable or uninstall other extensions',
geolocation: 'Can access physical location',
bookmarks: 'Can read and modify bookmarks',
'<all_urls>': 'Can access data on all websites you visit',
'*://*/*': 'Can access data on all websites you visit',
'http://*/*': 'Can access data on all HTTP websites',
'https://*/*': 'Can access data on all HTTPS websites',
activeTab: 'Can access the current active tab',
storage: 'Can store data locally',
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/report/LayerModal.jsx` around lines 28 - 50,
PERMISSION_RISKS is missing an entry for the backend-provided high-risk key
browsingData so users fall back to a generic message; add a specific mapping in
the PERMISSION_RISKS object (the same object that contains keys like webRequest
and cookies) with a plain-English explanation such as that browsingData can read
or clear browsing history, cache, cookies and other local browsing data,
ensuring the UI displays this high-risk description when high_risk_permissions
includes browsingData.


const CATEGORY_LABELS = {
code: 'Code Checks',
code: 'Code Checks',
threat: 'Threat Detection',
trust: 'Trust Signals',
trust: 'Trust Signals',
access: 'Permissions',
data: 'Data Handling',
data: 'Data Handling',
policy: 'Policies',
};

Expand Down Expand Up @@ -83,8 +107,8 @@ function bandLabel(band) {
switch (band) {
case 'GOOD': return 'Safe';
case 'WARN': return 'Needs Review';
case 'BAD': return 'Not Safe';
default: return '';
case 'BAD': return 'Not Safe';
default: return '';
}
}

Expand Down Expand Up @@ -152,9 +176,44 @@ const LayerModal = ({
style={{ animationDelay: `${(catIdx * 40 + (idx + 1) * 25)}ms` }}
role="listitem"
>
<div className="lm-check-left">
<span className="lm-check-name">{item.label}</span>
{item.desc && <InfoTooltip text={item.desc} />}
<div className="lm-check-left" style={{ flexDirection: 'column', alignItems: 'flex-start', gap: '4px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<span className="lm-check-name">{item.label}</span>
{item.desc && <InfoTooltip text={item.desc} />}
</div>
Comment on lines +179 to +183
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new inline style on .lm-check-left forces a column layout for all check rows, overriding the existing .lm-check-left flex row styling defined in LayerModal.scss. This is likely to change alignment/truncation behavior for checks that don’t have permission breakdowns. Consider keeping .lm-check-left’s SCSS-driven row layout and only switching to a column layout conditionally (e.g., when a breakdown is present) via a modifier class or an inner wrapper.

Copilot uses AI. Check for mistakes.

{/* Permission Risk Explanation Engine */}
{item.raw?.name === 'PermissionsBaseline' && item.raw?.details?.high_risk_permissions?.length > 0 && (
<div style={{ marginTop: '2px', paddingLeft: '8px', borderLeft: '2px solid var(--risk-warn)', fontSize: '11px', color: 'var(--theme-text-muted)', display: 'flex', flexDirection: 'column', gap: '4px' }}>
{item.raw.details.high_risk_permissions.map(perm => (
<div key={perm} style={{ display: 'flex', gap: '6px', lineHeight: '1.2' }}>
<span style={{ fontWeight: 600, color: 'var(--risk-warn)', flexShrink: 0 }}>{perm}</span>
<span style={{ opacity: 0.6 }}>&rarr;</span>
Comment on lines +187 to +191
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The permission breakdown uses a large inline style object (and the same style is duplicated again for the combos breakdown). To keep theming/maintenance consistent with the rest of LayerModal.scss, consider moving these styles into a dedicated CSS class and reusing it. Also, the border currently uses var(--risk-warn); the SCSS elsewhere uses --risk-warn-border (and often provides a fallback), which is likely the more appropriate token for borders.

Copilot uses AI. Check for mistakes.
<span>{PERMISSION_RISKS[perm] || `Has access to ${perm}`}</span>
</div>
))}
</div>
)}

{/* Dangerous Combos Explanation Engine */}
{item.raw?.name === 'PermissionCombos' && item.raw?.details?.triggered_combos?.length > 0 && (
<div style={{ marginTop: '2px', paddingLeft: '8px', borderLeft: '2px solid var(--risk-warn)', fontSize: '11px', color: 'var(--theme-text-muted)', display: 'flex', flexDirection: 'column', gap: '4px' }}>
{item.raw.details.triggered_combos.map(combo => {
const isBroad = combo === 'broad_host_access';
const comboName = isBroad ? 'Broad Host Access' : combo.split('+').join(' + ');
const comboDesc = isBroad
Comment on lines +202 to +204
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comboName is derived directly from the backend’s triggered_combos string. In src/extension_shield/scoring/normalizers.py, combos are built via "+".join(required_perms) where required_perms is a set, so the order can be non-deterministic. To avoid inconsistent UI labels across runs, consider normalizing on the frontend by splitting on +, sorting, then joining for display.

Copilot uses AI. Check for mistakes.
? 'Can access and modify data on all websites'
: 'High risk when these are used together';
Comment on lines +204 to +206
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says the “Dangerous Combos” section uses the same explanation engine to clearly state why each combination is high-risk, but the UI currently renders a generic description ('High risk when these are used together') for all non-broad-host combos. If the intent is to provide actionable context, consider mapping specific combos (or the combo’s constituent permissions via PERMISSION_RISKS) to concrete explanations rather than a one-size-fits-all message.

Copilot uses AI. Check for mistakes.
return (
<div key={combo} style={{ display: 'flex', gap: '6px', lineHeight: '1.2' }}>
<span style={{ fontWeight: 600, color: 'var(--risk-warn)', flexShrink: 0 }}>{comboName}</span>
<span style={{ opacity: 0.6 }}>&rarr;</span>
<span>{comboDesc}</span>
</div>
)
})}
</div>
)}
</div>
<span className="lm-status-wrap">
{item.statusType === 'clear' ? (
Expand Down
Loading