Skip to content
Merged
Show file tree
Hide file tree
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@
"export-to-csv": "node ./scripts/export-to-csv.js -f ./dist/codicon.ttf > ./dist/codicon.csv",
"copy-metadata": "cp ./src/template/metadata.json ./dist/metadata.json",
"embed-metadata": "node ./scripts/embed-metadata.js",
"embed-svg-data": "node ./scripts/embed-svg-data.js",
"check-metadata": "node ./scripts/check-metadata.js",
"fonts": "fantasticon",
"dev": "npm run build && npm run replace-in-vscode",
"build": "npm run clean && npm run svgo && npm run fonts && npm run export-to-ts && npm run export-to-csv && npm run copy-metadata && npm run embed-metadata && npm run sprite",
"build": "npm run clean && npm run svgo && npm run fonts && npm run export-to-ts && npm run export-to-csv && npm run copy-metadata && npm run embed-metadata && npm run embed-svg-data && npm run sprite",
"version:bump": "node ./scripts/version-bump.js",
"version:patch": "node ./scripts/version-bump.js patch minor",
"version:minor": "node ./scripts/version-bump.js minor minor",
Expand Down
31 changes: 31 additions & 0 deletions scripts/embed-svg-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const fs = require('fs');
const path = require('path');

const iconsDir = path.join(__dirname, '../src/icons');
const htmlPath = path.join(__dirname, '../dist/codicon.html');

if (!fs.existsSync(htmlPath)) {
console.error('HTML file not found:', htmlPath);
process.exit(1);
}

Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The script checks if the HTML file exists but doesn't verify that the icons directory exists before attempting to read it. This could result in a cryptic error if the directory is missing. Consider adding a check similar to the HTML file verification.

Suggested change
if (!fs.existsSync(iconsDir)) {
console.error('Icons directory not found:', iconsDir);
process.exit(1);
}

Copilot uses AI. Check for mistakes.
// Read all SVG files and build a data object
const svgData = {};
const svgFiles = fs.readdirSync(iconsDir).filter(file => file.endsWith('.svg'));

for (const file of svgFiles) {
const name = path.basename(file, '.svg');
const content = fs.readFileSync(path.join(iconsDir, file), 'utf8');
svgData[name] = content;
}

let html = fs.readFileSync(htmlPath, 'utf8');

if (html.includes('// SVG_DATA_PLACEHOLDER')) {
// Use regex to replace the initialization
html = html.replace(/let svgData = .*? \/\/ SVG_DATA_PLACEHOLDER/, `let svgData = ${JSON.stringify(svgData)}; // SVG_DATA_PLACEHOLDER`);
fs.writeFileSync(htmlPath, html);
console.log(`SVG data embedded into HTML (${Object.keys(svgData).length} icons).`);
} else {
console.warn('SVG data placeholder not found in HTML.');
}
118 changes: 104 additions & 14 deletions src/template/preview.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,6 @@
margin: 8px;
}

Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The removal of '.icon:hover { cursor: pointer; }' means icons no longer show a pointer cursor on hover, which might reduce discoverability of the copy functionality. Consider adding 'cursor: pointer' to the '.icon:hover .copy-buttons' selector or keeping a hover indicator on the icon itself.

Suggested change
.icon:hover {
cursor: pointer;
}

Copilot uses AI. Check for mistakes.
.icon:hover {
cursor: pointer;
}

.icon:hover .inner {
box-shadow: 0px 0px 12px var(--card-shadow-hover);
}
Expand Down Expand Up @@ -159,6 +155,45 @@
text-overflow: ellipsis;
}

.copy-buttons {
display: flex;
justify-content: center;
gap: 8px;
margin-top: 4px;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 0.2s ease-in-out;
}

.icon:hover .copy-buttons {
opacity: 1;
visibility: visible;
pointer-events: auto;
}

.copy-btn {
background: none;
border: none;
cursor: pointer;
padding: 4px;
color: var(--text-secondary);
border-radius: 4px;
transition: color 0.2s ease, background-color 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}

.copy-btn:hover {
color: var(--text-color);
background-color: var(--input-border);
}

.copy-btn i {
font-size: 14px;
}

.description {
display: none;
}
Expand Down Expand Up @@ -231,6 +266,14 @@
<br>
<span class='label'>{{ @key }}</span>
<span class='description'></span>
<div class="copy-buttons">
<button class="copy-btn copy-name-btn" title="Copy icon name" aria-label="Copy icon name">
<i class="codicon codicon-copy" aria-hidden="true"></i>
</button>
<button class="copy-btn copy-svg-btn" title="Copy SVG" aria-label="Copy SVG">
<i class="codicon codicon-file-code" aria-hidden="true"></i>
</button>
</div>
Comment on lines +269 to +276
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The copy buttons are placed inside the .icon div but outside any semantic grouping. Consider wrapping the buttons in a container with role="group" and aria-label="Copy actions" to provide better context for screen reader users about what these buttons do in relation to the icon.

Copilot uses AI. Check for mistakes.
</div>
{{/ each }}
</div>
Expand All @@ -248,6 +291,9 @@

// Load metadata
let metadata = {}; // METADATA_PLACEHOLDER

// Load SVG data
let svgData = {}; // SVG_DATA_PLACEHOLDER

if (Object.keys(metadata).length > 0) {
applyMetadata();
Expand Down Expand Up @@ -579,22 +625,66 @@
});
}

for(i=0;i<icons.length;i++){
let icon = icons[i]
icon.onclick = function(e){
let name = this.getAttribute('data-name');

copier.value = name;
// Copy functionality
function copyToClipboard(text, displayText) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(function() {
notificationText.innerHTML = displayText;
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The notification message uses the icon name directly in innerHTML without sanitization. While icon names are likely controlled data from the build process, if there's any possibility of user-controlled data in icon names, this could be an XSS vulnerability. Consider using textContent instead of innerHTML for displaying the icon name.

Copilot uses AI. Check for mistakes.

Check failure

Code scanning / CodeQL

DOM text reinterpreted as HTML High

DOM text
is reinterpreted as HTML without escaping meta-characters.
DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI 15 days ago

In general, the fix is to avoid interpreting untrusted strings as HTML. Instead of assigning potentially tainted text to element.innerHTML, use element.textContent (or an equivalent safe text API) so that any HTML meta-characters are treated as plain text rather than markup.

Concretely, in src/template/preview.hbs, the function copyToClipboard uses notificationText.innerHTML = displayText; in three places (success in the modern clipboard path, success in the fallback, and in the SVG fetch error path). We should change all of these assignments to use notificationText.textContent = displayText;. This preserves the existing behavior of displaying the same text to the user while preventing any embedded HTML inside displayText (or the literal 'SVG not available') from being interpreted as markup. No additional methods or imports are needed; textContent is a standard DOM property. This single change addresses both reported alert variants because both ultimately use displayText in the same sink.

Suggested changeset 1
src/template/preview.hbs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/template/preview.hbs b/src/template/preview.hbs
--- a/src/template/preview.hbs
+++ b/src/template/preview.hbs
@@ -629,7 +629,7 @@
     function copyToClipboard(text, displayText) {
         if (navigator.clipboard && navigator.clipboard.writeText) {
             navigator.clipboard.writeText(text).then(function() {
-                notificationText.innerHTML = displayText;
+                notificationText.textContent = displayText;
                 animateNotification();
             }).catch(function() {
                 // Fallback to old method
@@ -637,7 +637,7 @@
                 copier.select();
                 copier.setSelectionRange(0, 99999);
                 document.execCommand('copy');
-                notificationText.innerHTML = displayText;
+                notificationText.textContent = displayText;
                 animateNotification();
             });
         } else {
@@ -645,7 +645,7 @@
             copier.select();
             copier.setSelectionRange(0, 99999);
             document.execCommand('copy');
-            notificationText.innerHTML = displayText;
+            notificationText.textContent = displayText;
             animateNotification();
         }
     }
@@ -678,7 +678,7 @@
                     })
                     .catch(error => {
                         console.warn('Could not fetch SVG for ' + iconName, error);
-                        notificationText.innerHTML = 'SVG not available';
+                        notificationText.textContent = 'SVG not available';
                         animateNotification();
                     });
             }
EOF
@@ -629,7 +629,7 @@
function copyToClipboard(text, displayText) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(function() {
notificationText.innerHTML = displayText;
notificationText.textContent = displayText;
animateNotification();
}).catch(function() {
// Fallback to old method
@@ -637,7 +637,7 @@
copier.select();
copier.setSelectionRange(0, 99999);
document.execCommand('copy');
notificationText.innerHTML = displayText;
notificationText.textContent = displayText;
animateNotification();
});
} else {
@@ -645,7 +645,7 @@
copier.select();
copier.setSelectionRange(0, 99999);
document.execCommand('copy');
notificationText.innerHTML = displayText;
notificationText.textContent = displayText;
animateNotification();
}
}
@@ -678,7 +678,7 @@
})
.catch(error => {
console.warn('Could not fetch SVG for ' + iconName, error);
notificationText.innerHTML = 'SVG not available';
notificationText.textContent = 'SVG not available';
animateNotification();
});
}
Copilot is powered by AI and may make mistakes. Always verify output.
animateNotification();
}).catch(function() {
// Fallback to old method
copier.value = text;
copier.select();
copier.setSelectionRange(0, 99999);
document.execCommand('copy');
notificationText.innerHTML = displayText;

Check failure

Code scanning / CodeQL

DOM text reinterpreted as HTML High

DOM text
is reinterpreted as HTML without escaping meta-characters.
DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI 15 days ago

General approach: Avoid interpreting untrusted text as HTML. Instead of assigning displayText (which may come from data-name) to notificationText.innerHTML, assign it to notificationText.textContent so that any meta-characters are treated as literal text rather than markup. This preserves the visual behavior (showing a notification with the icon name or message) while eliminating the XSS sink.

Detailed best fix: In copyToClipboard, all three assignments to notificationText.innerHTML = displayText; should be changed to notificationText.textContent = displayText;. Likewise, in the SVG fetch catch handler, notificationText.innerHTML = 'SVG not available'; should be changed to notificationText.textContent = 'SVG not available';. These are all in src/template/preview.hbs around lines 631–641 and 679–682. No additional functions or imports are needed; textContent is a standard DOM property and behaves the same across modern browsers. This change does not alter the logic except to prevent HTML parsing of the message text.

Suggested changeset 1
src/template/preview.hbs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/template/preview.hbs b/src/template/preview.hbs
--- a/src/template/preview.hbs
+++ b/src/template/preview.hbs
@@ -629,7 +629,7 @@
     function copyToClipboard(text, displayText) {
         if (navigator.clipboard && navigator.clipboard.writeText) {
             navigator.clipboard.writeText(text).then(function() {
-                notificationText.innerHTML = displayText;
+                notificationText.textContent = displayText;
                 animateNotification();
             }).catch(function() {
                 // Fallback to old method
@@ -637,7 +637,7 @@
                 copier.select();
                 copier.setSelectionRange(0, 99999);
                 document.execCommand('copy');
-                notificationText.innerHTML = displayText;
+                notificationText.textContent = displayText;
                 animateNotification();
             });
         } else {
@@ -645,7 +645,7 @@
             copier.select();
             copier.setSelectionRange(0, 99999);
             document.execCommand('copy');
-            notificationText.innerHTML = displayText;
+            notificationText.textContent = displayText;
             animateNotification();
         }
     }
@@ -678,7 +678,7 @@
                     })
                     .catch(error => {
                         console.warn('Could not fetch SVG for ' + iconName, error);
-                        notificationText.innerHTML = 'SVG not available';
+                        notificationText.textContent = 'SVG not available';
                         animateNotification();
                     });
             }
EOF
@@ -629,7 +629,7 @@
function copyToClipboard(text, displayText) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(function() {
notificationText.innerHTML = displayText;
notificationText.textContent = displayText;
animateNotification();
}).catch(function() {
// Fallback to old method
@@ -637,7 +637,7 @@
copier.select();
copier.setSelectionRange(0, 99999);
document.execCommand('copy');
notificationText.innerHTML = displayText;
notificationText.textContent = displayText;
animateNotification();
});
} else {
@@ -645,7 +645,7 @@
copier.select();
copier.setSelectionRange(0, 99999);
document.execCommand('copy');
notificationText.innerHTML = displayText;
notificationText.textContent = displayText;
animateNotification();
}
}
@@ -678,7 +678,7 @@
})
.catch(error => {
console.warn('Could not fetch SVG for ' + iconName, error);
notificationText.innerHTML = 'SVG not available';
notificationText.textContent = 'SVG not available';
animateNotification();
});
}
Copilot is powered by AI and may make mistakes. Always verify output.
animateNotification();
});
} else {
copier.value = text;
copier.select();
copier.setSelectionRange(0, 99999)
copier.setSelectionRange(0, 99999);
document.execCommand('copy');

notificationText.innerHTML = name;
notificationText.innerHTML = displayText;

Check failure

Code scanning / CodeQL

DOM text reinterpreted as HTML High

DOM text
is reinterpreted as HTML without escaping meta-characters.
DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI 15 days ago

In general, to fix “DOM text reinterpreted as HTML” issues, avoid passing untrusted strings to APIs that interpret them as HTML (innerHTML, outerHTML, jQuery’s html(), etc.). Instead, insert them as plain text using textContent, innerText, or appropriate escaping, so that any meta-characters are rendered literally rather than executed.

For this specific code, the displayText argument to copyToClipboard is derived from data-name and then used for user-visible notifications only. There is no requirement here to support HTML formatting in the notification; it should just display plain text like the icon name or “SVG not available”. Therefore, the best fix is to replace notificationText.innerHTML = displayText; with notificationText.textContent = displayText; in all places where displayText (or a string built from it) is shown. This preserves all current behavior (user still sees the same message, clipboard functionality is unchanged), but ensures that any special characters in data-name are not interpreted as HTML.

Concretely, in src/template/preview.hbs:

  • In the copyToClipboard function:
    • On the success path of navigator.clipboard.writeText, change notificationText.innerHTML = displayText; to notificationText.textContent = displayText;.
    • On the catch (fallback) path, change the second notificationText.innerHTML = displayText; similarly.
    • In the branch for environments without navigator.clipboard, change notificationText.innerHTML = displayText; likewise.
  • In the fetch error handler in the SVG copy code, change notificationText.innerHTML = 'SVG not available'; to notificationText.textContent = 'SVG not available';.

No new methods, imports, or definitions are required; textContent is a standard DOM property.


Suggested changeset 1
src/template/preview.hbs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/template/preview.hbs b/src/template/preview.hbs
--- a/src/template/preview.hbs
+++ b/src/template/preview.hbs
@@ -629,7 +629,7 @@
     function copyToClipboard(text, displayText) {
         if (navigator.clipboard && navigator.clipboard.writeText) {
             navigator.clipboard.writeText(text).then(function() {
-                notificationText.innerHTML = displayText;
+                notificationText.textContent = displayText;
                 animateNotification();
             }).catch(function() {
                 // Fallback to old method
@@ -637,7 +637,7 @@
                 copier.select();
                 copier.setSelectionRange(0, 99999);
                 document.execCommand('copy');
-                notificationText.innerHTML = displayText;
+                notificationText.textContent = displayText;
                 animateNotification();
             });
         } else {
@@ -645,7 +645,7 @@
             copier.select();
             copier.setSelectionRange(0, 99999);
             document.execCommand('copy');
-            notificationText.innerHTML = displayText;
+            notificationText.textContent = displayText;
             animateNotification();
         }
     }
@@ -678,7 +678,7 @@
                     })
                     .catch(error => {
                         console.warn('Could not fetch SVG for ' + iconName, error);
-                        notificationText.innerHTML = 'SVG not available';
+                        notificationText.textContent = 'SVG not available';
                         animateNotification();
                     });
             }
EOF
@@ -629,7 +629,7 @@
function copyToClipboard(text, displayText) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(function() {
notificationText.innerHTML = displayText;
notificationText.textContent = displayText;
animateNotification();
}).catch(function() {
// Fallback to old method
@@ -637,7 +637,7 @@
copier.select();
copier.setSelectionRange(0, 99999);
document.execCommand('copy');
notificationText.innerHTML = displayText;
notificationText.textContent = displayText;
animateNotification();
});
} else {
@@ -645,7 +645,7 @@
copier.select();
copier.setSelectionRange(0, 99999);
document.execCommand('copy');
notificationText.innerHTML = displayText;
notificationText.textContent = displayText;
animateNotification();
}
}
@@ -678,7 +678,7 @@
})
.catch(error => {
console.warn('Could not fetch SVG for ' + iconName, error);
notificationText.innerHTML = 'SVG not available';
notificationText.textContent = 'SVG not available';
animateNotification();
});
}
Copilot is powered by AI and may make mistakes. Always verify output.
animateNotification();
e.preventDefault();
}
}

// Setup copy buttons for each icon
for(let i=0;i<icons.length;i++){
let icon = icons[i];

// Copy name button
let copyNameBtn = icon.querySelector('.copy-name-btn');
copyNameBtn.onclick = function(e){
e.stopPropagation();
let iconName = this.closest('.icon').getAttribute('data-name');
copyToClipboard(iconName, iconName);
};

// Copy SVG button
let copySvgBtn = icon.querySelector('.copy-svg-btn');
copySvgBtn.onclick = function(e){
e.stopPropagation();
let iconName = this.closest('.icon').getAttribute('data-name');
if (svgData[iconName]) {
copyToClipboard(svgData[iconName], iconName + ' (SVG)');
} else {
// Fallback: try to fetch SVG if not in embedded data
fetch('icons/' + iconName + '.svg')
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The fetch fallback for SVG data uses a relative path 'icons/' + iconName + '.svg' but there's no verification that this path is correct or that the icons directory is available at this location in the deployed site. Consider documenting where these files should be located or ensuring the build process copies them to the expected location.

This issue also appears in the following locations of the same file:

  • line 670

Copilot uses AI. Check for mistakes.
.then(response => response.text())
.then(svg => {
copyToClipboard(svg, iconName + ' (SVG)');
})
.catch(error => {
console.warn('Could not fetch SVG for ' + iconName, error);
notificationText.innerHTML = 'SVG not available';
animateNotification();
});
}
};
Comment on lines +659 to +685
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The code doesn't check if the querySelector calls return null before assigning onclick handlers. If the template structure changes or an icon doesn't have the expected buttons, this will throw an error. Add null checks before assigning the onclick handlers.

Suggested change
copyNameBtn.onclick = function(e){
e.stopPropagation();
let iconName = this.closest('.icon').getAttribute('data-name');
copyToClipboard(iconName, iconName);
};
// Copy SVG button
let copySvgBtn = icon.querySelector('.copy-svg-btn');
copySvgBtn.onclick = function(e){
e.stopPropagation();
let iconName = this.closest('.icon').getAttribute('data-name');
if (svgData[iconName]) {
copyToClipboard(svgData[iconName], iconName + ' (SVG)');
} else {
// Fallback: try to fetch SVG if not in embedded data
fetch('icons/' + iconName + '.svg')
.then(response => response.text())
.then(svg => {
copyToClipboard(svg, iconName + ' (SVG)');
})
.catch(error => {
console.warn('Could not fetch SVG for ' + iconName, error);
notificationText.innerHTML = 'SVG not available';
animateNotification();
});
}
};
if (copyNameBtn) {
copyNameBtn.onclick = function(e){
e.stopPropagation();
let iconName = this.closest('.icon').getAttribute('data-name');
copyToClipboard(iconName, iconName);
};
}
// Copy SVG button
let copySvgBtn = icon.querySelector('.copy-svg-btn');
if (copySvgBtn) {
copySvgBtn.onclick = function(e){
e.stopPropagation();
let iconName = this.closest('.icon').getAttribute('data-name');
if (svgData[iconName]) {
copyToClipboard(svgData[iconName], iconName + ' (SVG)');
} else {
// Fallback: try to fetch SVG if not in embedded data
fetch('icons/' + iconName + '.svg')
.then(response => response.text())
.then(svg => {
copyToClipboard(svg, iconName + ' (SVG)');
})
.catch(error => {
console.warn('Could not fetch SVG for ' + iconName, error);
notificationText.innerHTML = 'SVG not available';
animateNotification();
});
}
};
}

Copilot uses AI. Check for mistakes.
}

function animateNotification(){
window.clearTimeout(timer);
search.focus();
Expand Down
Loading