Skip to content

Conversation

@marklearst
Copy link
Owner

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

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
Copilot AI review requested due to automatic review settings December 30, 2025 00:26
@marklearst marklearst merged commit c8bf21c into main Dec 30, 2025
6 checks passed
@codecov
Copy link

codecov bot commented Dec 30, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.90%. Comparing base (c96a36e) to head (1d6c303).
⚠️ Report is 4 commits behind head on main.

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              
Flag Coverage Δ
unittests 90.90% <100.00%> (+0.43%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@marklearst marklearst deleted the feat/token-redaction-utility branch December 30, 2025 00:28
Copy link

Copilot AI left a 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)
Copy link

Copilot AI Dec 30, 2025

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).

Suggested change
const end = token.slice(-visibleEnd)
const end = visibleEnd === 0 ? '' : token.slice(-visibleEnd)

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +53
const {
visibleStart = 5,
visibleEnd = 3,
emptyPlaceholder = '[no token]',
} = options ?? {}

Copy link

Copilot AI Dec 30, 2025

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.

Suggested change
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)
}

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +73
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')
})
})
Copy link

Copilot AI Dec 30, 2025

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

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +33
* 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.
Copy link

Copilot AI Dec 30, 2025

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.

Suggested change
* 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.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +42
/**
* 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
}
Copy link

Copilot AI Dec 30, 2025

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.

Copilot uses AI. Check for mistakes.
marklearst added a commit that referenced this pull request Dec 30, 2025
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant