Skip to content

security: implement credential masking via XOR to protect sensitive user data in RAM. - #2941

Closed
dev12124 wants to merge 3 commits into
Acode-Foundation:mainfrom
dev12124:main
Closed

dev12124 wants to merge 3 commits into
Acode-Foundation:mainfrom
dev12124:main

Conversation

@dev12124

Copy link
Copy Markdown

This PR introduces a security enhancement to mitigate credential scraping vulnerabilities in memory (RAM).

Plain-text variables containing sensitive user info (such as emails, GitHub tokens, and websites) are vulnerable to inspection or tampering by potentially malicious third-party plugins running within the same environment.

Changes implemented:

  • Added a security module (security.js) featuring simple and lightweight dynamic masking using a fixed XOR key (0x5A).
  • Integrated secureUserObject and getDecryptedUser inside auth.js to ensure data remains obfuscated while stored in RAM/localStorage, preventing crashes during UI rendering.

I´m added a Mask using 0x5A for have a mask in the Critic Variables. e.g:
I´m create a Variable using let named "secret_key" with attribute "secret_password". This Variable is in the RAM, and a Malware-Plugin installed have access this Variable and modify. And, e.g: The Variable loggedInUser have a Critic Properties ("email", "github", "website") and this Malware-Plugin access and view this Variable.
…ser data in RAM

This PR introduces a security enhancement to mitigate credential scraping vulnerabilities in memory (RAM).

Plain-text variables containing sensitive user info (such as emails, GitHub tokens, and websites) are vulnerable to inspection or tampering by potentially malicious third-party plugins running within the same environment.

Changes implemented:
- Added a security module (`security.js`) featuring simple and lightweight dynamic masking using a fixed XOR key (0x5A).
- Integrated `secureUserObject` and `getDecryptedUser` inside `auth.js` to ensure data remains obfuscated while stored in RAM/localStorage, preventing crashes during UI rendering.
@greptile-apps

greptile-apps Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 1/5

[Medium risk] Adds XOR masking to user object properties in memory.

The PR is not safe to merge because cached users break on fetch failure, some names are corrupted, and the masking does not protect against malicious plugins.

Findings

  1. P1 Cached user remains masked ▶
  2. P1 Masking corrupts Unicode characters ▶
  3. P1 Security Fixed key exposes cached data ▶

Summary

The PR masks selected user-profile strings in memory and local storage, then unmasks them for normal auth callers.

  • The fetch-failure cache path does not unmask them.
  • The string transformation loses supplementary Unicode characters.
  • A fixed key in the same page as plugins does not provide the intended protection against malicious plugins.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["/login response"] --> B["Mask profile strings"]
  B --> C["In-memory user"]
  B --> D["localStorage cache"]
  C --> E["Unmask on normal return"]
  D --> F["Fetch-failure fallback"]
  F --> G["Return masked arrays to UI"]
Loading

Reviews (1) · Last reviewed commit: "security: implement credential masking v..."

Comment thread src/lib/auth.js
return loggedInUser;
const rawuser = await res.json();
loggedInUser = secureUserObject(rawuser);
localStorage.setItem(CACHE_USER_KEY, JSON.stringify(loggedInUser));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Cached user remains masked After a successful login saves masked fields to the cache, a later /login request that fails, such as while offline, returns that cache without decrypting it. Callers receive arrays instead of strings, so the sidebar can display numbers for the email or throw while generating an avatar from the name.

Comment thread src/security/security.js
if (!secretString) return [];

// Transforms the string into a masked array of bytes (numbers)
return Array.from(secretString).map(char => char.charCodeAt(0) ^ MASK_KEY);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Masking corrupts Unicode characters When a profile field contains an emoji or another supplementary Unicode character, Array.from keeps it as one element but charCodeAt(0) records only half of it. Unmasking cannot restore the original character, so the displayed value is corrupted and the corrupted form is saved in the cache.

Suggested change
return Array.from(secretString).map(char => char.charCodeAt(0) ^ MASK_KEY);
return secretString.split("").map(char => char.charCodeAt(0) ^ MASK_KEY);

Comment thread src/security/security.js
* reside in the RAM unprotected, making them vulnerable to access or modification
* by malicious installed plugins.
*/
const MASK_KEY = 0x5A;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 security Fixed key exposes cached data A malicious plugin runs as a script in the application page and can read localStorage. It can reverse each stored number using the fixed 0x5A key in the client code, recovering the email and other masked fields. The masking therefore does not provide the intended protection against malicious plugins.

How this was verified: Plugin scripts share the page that writes the masked cache, and the stored numbers are reversed with the fixed key present in the client code.

Knowledge Base Used: Plugins and platform services

@dev12124 dev12124 closed this Sep 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant