DEV Community

Sho Naka
Sho Naka

Posted on

My menu bar clock disappeared after a macOS update. I wrote a 50-line VS Code extension instead of trusting a marketplace one.

Every setting I checked said the clock should render. NSStatusItem Visible Clock was 1. _HIHideMenuBar was 0. Stage Manager was off. No MDM profile. All correct. The clock still wasn't there.

TL;DR: Rather than trust an unknown VS Code Marketplace extension after two real 2024 supply-chain incidents, I wrote my own status bar clock — 50 lines, zero external dependencies, deployed with one symlink. 30 minutes total.

Quick answer

If a tool feature is tiny, security-sensitive, and easy to implement with the host application's API, the safer option may be to write a local-only version instead of installing a marketplace extension. In this case, a VS Code status bar clock needed only the vscode API, Date, and setInterval, so a local extension with zero dependencies was easier to audit than a third-party package.

What actually happened

Right after a macOS 26.5.1 (Tahoe) update, the menu bar clock vanished. I went through the settings systematically:

defaults read com.apple.controlcenter
# NSStatusItem Visible Clock = 1
# _HIHideMenuBar = 0
# Stage Manager: disabled
# MDM profile: none
Enter fullscreen mode Exit fullscreen mode

Every value was what it should be. The clock still didn't render.

I looked at a third-party menu bar clock app (Itsycal) — pointless if the menu bar itself won't show anything. I looked at clock extensions on the VS Code Marketplace next. That's where I stopped.

The number that changed my mind

Two real incidents from 2024, not hypotheticals:

  • May 2024: a fake "Prettier" extension on the VS Code Marketplace shipped a PowerShell backdoor
  • October 2024: the "Material Theme" extension — millions of downloads — had its publisher account compromised, and a malicious version went out under the same trusted name

Marketplace review is closer to npm's than to Apple's App Store: no meaningful pre-publication vetting, and download-count inflation has been documented. A "verified" badge doesn't protect against an account takeover — the account is verified; the person controlling it after a breach is not.

For a feature this small — showing the time — the ongoing cost of vetting a publisher, reviewing source, and re-reviewing every update didn't make sense. This is the same shape of problem as npm's event-stream incident (2018) or the repeated PyPI typosquatting campaigns: open ecosystem, thin review, and a single compromised maintainer account is enough.

The rule I extracted

Code you can read completely is the only dependency you can actually vet. Everything else is trust by proxy.

A self-written extension has three properties a third-party one can't offer:

  1. Fully readable: under 50 lines total across package.json and extension.js. One minute to read end to end
  2. Zero external dependencies: only the vscode API and Node's standard library. No npm install pulling in a transitive dependency tree
  3. No distribution channel: it lives in ~/.vscode/extensions/ on one machine, never published to a Marketplace. There's no publish pipeline for an attacker to compromise

Before you install a tiny extension

Use this shortlist before installing a marketplace extension for a small convenience feature:

  • Can I describe the entire feature in one sentence?
  • Can I implement it with the host application's built-in API and standard library only?
  • Can I read the complete code in under five minutes?
  • Does the extension need network, filesystem, shell, or credential access?
  • Would an automatic update create a new trust decision I will not actually review?

If the feature is small and the answer to the first three questions is yes, a local-only version may be less work than ongoing dependency review.

Try it yourself

package.json (20 lines):

{
  "name": "statusbar-clock",
  "displayName": "Statusbar Clock",
  "description": "Show current date/time in VS Code status bar (local-only, zero dependencies)",
  "version": "0.1.0",
  "publisher": "local-only",
  "private": true,
  "engines": { "vscode": "^1.80.0" },
  "main": "./src/extension.js",
  "activationEvents": ["onStartupFinished"],
  "contributes": {
    "configuration": {
      "title": "Statusbar Clock",
      "properties": {
        "statusbarClock.format": {
          "type": "string",
          "default": "yyyy-MM-dd (EEE) HH:mm:ss",
          "description": "Display format. Tokens: yyyy MM dd HH mm ss EEE(weekday)"
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

extension.js (30 lines):

const vscode = require('vscode');
const WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

function pad(n) { return String(n).padStart(2, '0'); }

function formatDate(fmt, d) {
  return fmt
    .replace('yyyy', d.getFullYear())
    .replace('MM', pad(d.getMonth() + 1))
    .replace('dd', pad(d.getDate()))
    .replace('HH', pad(d.getHours()))
    .replace('mm', pad(d.getMinutes()))
    .replace('ss', pad(d.getSeconds()))
    .replace('EEE', WEEKDAYS[d.getDay()]);
}

let statusBarItem, timer;

function activate(context) {
  statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 1000);
  statusBarItem.show();
  context.subscriptions.push(statusBarItem);

  const update = () => {
    const fmt = vscode.workspace.getConfiguration('statusbarClock').get('format');
    statusBarItem.text = formatDate(fmt, new Date());
  };
  update();
  timer = setInterval(update, 1000);
  context.subscriptions.push({ dispose: () => clearInterval(timer) });
}

function deactivate() {
  if (timer) clearInterval(timer);
  if (statusBarItem) statusBarItem.dispose();
}

module.exports = { activate, deactivate };
Enter fullscreen mode Exit fullscreen mode

Three VS Code APIs used, total: createStatusBarItem, .text =, getConfiguration().get(). No network access, no filesystem writes, no child_process. The extension physically cannot do anything besides display a string — if my code has a bug, it's not a security incident.

Normal VS Code extensions get packaged with vsce package into a .vsix and installed via code --install-extension. My local vsce was broken (a CJS loader MODULE_NOT_FOUND from a Node version mismatch), so I skipped packaging entirely:

EXT_DIR="$HOME/.vscode/extensions/local-only.statusbar-clock-0.1.0"
mkdir -p "$HOME/.vscode/extensions"
ln -s /path/to/vscode-statusbar-clock "$EXT_DIR"
Enter fullscreen mode Exit fullscreen mode

The directory name convention is <publisher>.<name>-<version>, matching the three fields in package.json. Fully quit VS Code (not just close the window) and relaunch — the clock appears in the status bar, updating every second.

What's the smallest dependency tree you'd need to trust before a "verified" badge on a marketplace listing stops meaning anything to you?

FAQ

Is writing my own VS Code extension always safer?

No. It is safer only when the feature is small enough to read completely and can be built without external dependencies. For large features, you can easily replace marketplace risk with your own untested code.

Why not install a trusted marketplace extension and pin the version?

Pinning helps, but it does not remove the original trust decision. You still need to trust the publisher, the package contents, the update path, and any dependency tree the extension brings with it.

Does a local symlinked extension avoid every supply-chain risk?

No. It only removes marketplace distribution and automatic update risk. You still need to read the code, keep the directory local, and avoid adding dependencies that recreate the same problem through npm.


Sho Naka (nomurasan). I help companies adopt AI day to day; this is one of the small self-contained fixes that came out of my own dev environment.

This piece was adapted from a Japanese essay, with AI assistance for the cross-language rewrite. The reasoning, data, and conclusions are mine.

Originally published in Japanese at qiita.com/nomurasan. I write under "nomuraya / shimajima / 中翔" — the same person across media.

Top comments (0)