-
-
Notifications
You must be signed in to change notification settings - Fork 0
feat(utils): add token redaction utility for safe logging #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Add redactToken() function to safely mask Figma Personal Access Tokens
for logging and debugging purposes without exposing sensitive credentials.
Features:
- Preserves first 5 and last 3 characters for identification
- Masks middle portion with '***...***' pattern
- Handles null/undefined/empty tokens with configurable placeholder
- Fully masks tokens that are too short to redact meaningfully
- Configurable via options: visibleStart, visibleEnd, emptyPlaceholder
Usage:
import { redactToken } from '@figma-vars/hooks/utils';
console.log(redactToken('figd_abc123xyz789def456'));
// Output: 'figd_***...***456'
console.log(redactToken(null));
// Output: '[no token]'
Changes:
- src/utils/redactToken.ts: New utility with RedactTokenOptions interface
- src/utils/index.ts: Export redactToken and RedactTokenOptions
- tests/utils/redactToken.test.ts: 14 tests covering all branches
Test coverage: 100%
Refs: Codex Audit Item #17
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #13 +/- ##
==========================================
+ Coverage 90.47% 90.90% +0.43%
==========================================
Files 34 35 +1
Lines 903 935 +32
Branches 246 263 +17
==========================================
+ Hits 817 850 +33
+ Misses 85 84 -1
Partials 1 1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a new redactToken() utility function to safely mask Figma Personal Access Tokens for logging and debugging purposes, preventing accidental exposure of sensitive credentials in logs.
Key changes:
- New utility function with configurable visible start/end characters and placeholder text
- Comprehensive test suite with 14 test cases covering various scenarios
- Exports added to the utils barrel file for external consumption
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/utils/redactToken.ts | Implements the core redaction logic with RedactTokenOptions interface for customization |
| src/utils/index.ts | Exports redactToken function and RedactTokenOptions type from utils barrel |
| tests/utils/redactToken.test.ts | Comprehensive test suite covering basic redaction, null/undefined handling, short tokens, custom options, and edge cases |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| const start = token.slice(0, visibleStart) | ||
| const end = token.slice(-visibleEnd) |
Copilot
AI
Dec 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a critical bug when visibleEnd is 0. The expression slice(-0) is equivalent to slice(0), which returns the entire string from the beginning, effectively exposing the full token instead of hiding it. This defeats the security purpose of the function.
To fix this, add a conditional check: if visibleEnd is 0, use an empty string for end. Similarly, ensure visibleStart works correctly when 0 (it already does, since slice(0, 0) returns an empty string).
| const end = token.slice(-visibleEnd) | |
| const end = visibleEnd === 0 ? '' : token.slice(-visibleEnd) |
| const { | ||
| visibleStart = 5, | ||
| visibleEnd = 3, | ||
| emptyPlaceholder = '[no token]', | ||
| } = options ?? {} | ||
|
|
Copilot
AI
Dec 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function doesn't validate that visibleStart and visibleEnd are non-negative numbers. Negative values could lead to unexpected behavior with string slicing. For example, passing visibleStart: -5 would cause slice(0, -5) which removes the last 5 characters instead of showing the first -5 characters.
Consider adding validation to ensure both values are non-negative, or document the expected behavior when negative values are provided.
| const { | |
| visibleStart = 5, | |
| visibleEnd = 3, | |
| emptyPlaceholder = '[no token]', | |
| } = options ?? {} | |
| const emptyPlaceholder = options?.emptyPlaceholder ?? '[no token]' | |
| let visibleStart = options?.visibleStart | |
| if (typeof visibleStart !== 'number' || !Number.isFinite(visibleStart) || visibleStart < 0) { | |
| visibleStart = 5 | |
| } else { | |
| visibleStart = Math.floor(visibleStart) | |
| } | |
| let visibleEnd = options?.visibleEnd | |
| if (typeof visibleEnd !== 'number' || !Number.isFinite(visibleEnd) || visibleEnd < 0) { | |
| visibleEnd = 3 | |
| } else { | |
| visibleEnd = Math.floor(visibleEnd) | |
| } |
| describe('custom options', () => { | ||
| it('should use custom visibleStart', () => { | ||
| const result = redactToken('abcdefghijklmnop', { visibleStart: 3 }) | ||
| expect(result).toBe('abc***...***nop') | ||
| }) | ||
|
|
||
| it('should use custom visibleEnd', () => { | ||
| const result = redactToken('abcdefghijklmnop', { visibleEnd: 5 }) | ||
| expect(result).toBe('abcde***...***lmnop') | ||
| }) | ||
|
|
||
| it('should use custom emptyPlaceholder', () => { | ||
| const result = redactToken(null, { emptyPlaceholder: 'N/A' }) | ||
| expect(result).toBe('N/A') | ||
| }) | ||
|
|
||
| it('should use all custom options together', () => { | ||
| const result = redactToken('abcdefghijklmnop', { | ||
| visibleStart: 2, | ||
| visibleEnd: 2, | ||
| emptyPlaceholder: 'custom', | ||
| }) | ||
| expect(result).toBe('ab***...***op') | ||
| }) | ||
| }) |
Copilot
AI
Dec 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test coverage is missing for the critical edge case where visibleEnd is set to 0. This would expose the entire token due to how slice(-0) behaves in JavaScript. Add a test case like:
expect(redactToken('abcdefghij', { visibleEnd: 0 })).toBe('abcde***...***')
Also add tests for:
visibleStart: 0(should work correctly but needs verification)- Negative values for both options to document/verify expected behavior
- Both options set to 0 simultaneously
| * Number of characters to show at the start of the token. | ||
| * @defaultValue 5 | ||
| */ | ||
| visibleStart?: number | ||
| /** | ||
| * Number of characters to show at the end of the token. |
Copilot
AI
Dec 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The interface documentation should specify that visibleStart and visibleEnd must be non-negative integers. Currently, there's no indication of valid ranges or constraints, which could lead to misuse. Consider adding a note in the JSDoc comments for both properties.
| * Number of characters to show at the start of the token. | |
| * @defaultValue 5 | |
| */ | |
| visibleStart?: number | |
| /** | |
| * Number of characters to show at the end of the token. | |
| * Number of characters to show at the start of the token. | |
| * Must be a non-negative integer. | |
| * @defaultValue 5 | |
| */ | |
| visibleStart?: number | |
| /** | |
| * Number of characters to show at the end of the token. | |
| * Must be a non-negative integer. |
| /** | ||
| * Redact a Figma Personal Access Token for safe logging. | ||
| * | ||
| * @remarks | ||
| * This utility masks the middle portion of a token while preserving | ||
| * the first and last few characters for identification. Useful for | ||
| * debugging and logging without exposing sensitive credentials. | ||
| * | ||
| * @param token - The token to redact (can be null/undefined) | ||
| * @param options - Optional configuration for redaction behavior | ||
| * @returns The redacted token string, or a placeholder for empty/short tokens | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { redactToken } from '@figma-vars/hooks/utils'; | ||
| * | ||
| * console.log(redactToken('figd_abc123xyz789def456')); | ||
| * // Output: 'figd_***...***456' | ||
| * | ||
| * console.log(redactToken(null)); | ||
| * // Output: '[no token]' | ||
| * ``` | ||
| * | ||
| * @public | ||
| */ | ||
| export interface RedactTokenOptions { | ||
| /** | ||
| * Number of characters to show at the start of the token. | ||
| * @defaultValue 5 | ||
| */ | ||
| visibleStart?: number | ||
| /** | ||
| * Number of characters to show at the end of the token. | ||
| * @defaultValue 3 | ||
| */ | ||
| visibleEnd?: number | ||
| /** | ||
| * Placeholder text for null/undefined tokens. | ||
| * @defaultValue '[no token]' | ||
| */ | ||
| emptyPlaceholder?: string | ||
| } |
Copilot
AI
Dec 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The JSDoc comment on lines 1-25 appears to document the redactToken function, but it's placed above the RedactTokenOptions interface. This is unconventional and could confuse documentation generators.
The JSDoc comment should be moved to directly above the function declaration (line 44), and the interface should have its own separate JSDoc comment explaining what it represents. This follows the pattern seen in other files like src/utils/swrKeys.ts where interfaces have their own documentation.
Add caseInsensitive option to filterVariables() for flexible name matching,
enabling case-agnostic search in variable filtering scenarios.
Problem:
- Users searching for 'color' wouldn't find 'Primary Color'
- No way to perform case-insensitive variable name searches
- Common UX pattern in search interfaces not supported
Solution:
- Added caseInsensitive boolean option to FilterVariablesCriteria
- When true, both variable name and search term are lowercased
- Defaults to false for backward compatibility
Usage:
import { filterVariables } from '@figma-vars/hooks';
// Case-insensitive search
filterVariables(vars, { name: 'color', caseInsensitive: true });
// Matches: 'Primary Color', 'COLOR_BG', 'MyColor'
Changes:
- src/utils/filterVariables.ts: Added caseInsensitive option + interface
- src/utils/index.ts: Export FilterVariablesCriteria type
- tests/utils/filterVariables.test.ts: Added 6 tests for case-insensitive
Test coverage: 100%
Refs: Codex Audit Item #13
Add redactToken() function to safely mask Figma Personal Access Tokens for logging and debugging purposes without exposing sensitive credentials.
Features:
Usage:
import { redactToken } from '@figma-vars/hooks/utils';
console.log(redactToken('figd_abc123xyz789def456')); // Output: 'figd_***...***456'
console.log(redactToken(null)); // Output: '[no token]'
Changes:
Test coverage: 100%
Refs: Codex Audit Item #17