Skip to content

Conversation

@leoafarias
Copy link
Collaborator

  • T1/T2: Align SDK constraints (>=3.9.0) and fix mix version conflict (1.x vs 2.x)
  • T4: Fix watch task lifecycle bug - tasks were disposed after every build,
    causing reuse of disposed tasks in watch mode. Added DeckBuilder.dispose()
  • T10: Fix path validation false positives - now checks for '..' as path
    segment, not substring (allows '..config.png', rejects '../secret')
  • T12: Fix browser initialization race condition - concurrent calls to
    _getBrowser() now share initialization future instead of launching
    multiple browsers
  • Add code review fixes plan to .planning/

https://claude.ai/code/session_017yfkWSkRfgTpmtpafCRkRq

- T1/T2: Align SDK constraints (>=3.9.0) and fix mix version conflict (1.x vs 2.x)
- T4: Fix watch task lifecycle bug - tasks were disposed after every build,
  causing reuse of disposed tasks in watch mode. Added DeckBuilder.dispose()
- T10: Fix path validation false positives - now checks for '..' as path
  segment, not substring (allows '..config.png', rejects '../secret')
- T12: Fix browser initialization race condition - concurrent calls to
  _getBrowser() now share initialization future instead of launching
  multiple browsers
- Add code review fixes plan to .planning/

https://claude.ai/code/session_017yfkWSkRfgTpmtpafCRkRq
- Remove mix/mix_generator from root pubspec (mix_generator has no 2.x
  version; melos.yaml bootstrap handles dependency overrides for packages)
- Fix uri_validator path traversal check to use raw string splitting
  instead of parsed URI segments, since Uri.parse() resolves '..' before
  we can detect it (e.g., file:///../../../etc/passwd -> file:///etc/passwd)
- Also catches URL-encoded traversal (%2e%2e)

All tests pass: 1380 passed, 11 skipped, 0 failures
Analyze: no issues across all 5 packages

https://claude.ai/code/session_017yfkWSkRfgTpmtpafCRkRq
Keep mix: ^2.0.0-rc.0 in root pubspec as intended. The earlier bootstrap
failure was due to Flutter SDK not being initialized; now resolves correctly
via FVM symlink. mix_generator remains removed (no 2.x version exists).

https://claude.ai/code/session_017yfkWSkRfgTpmtpafCRkRq
@docs-page
Copy link

docs-page bot commented Jan 28, 2026

To view this pull requests documentation preview, visit the following URL:

docs.page/btwld/superdeck~32

Documentation is deployed and generated using docs.page.

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 addresses critical code review issues identified for the 1.0 release, focusing on SDK constraints, lifecycle management, security improvements, and concurrency fixes.

Changes:

  • Updated SDK constraints to >=3.9.0 and upgraded mix package from 1.7.0 to 2.0.0-rc.0
  • Fixed watch mode resource leaks by adding DeckBuilder.dispose() method and proper cleanup
  • Enhanced path traversal validation to check '..' as path segments rather than substrings
  • Resolved browser initialization race condition in MermaidGenerator using shared futures

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pubspec.yaml Updated SDK constraints and mix package version from 1.x to 2.x
pubspec.lock Updated lockfile reflecting new dependencies and SDK constraints
packages/superdeck/lib/src/utils/uri_validator.dart Improved path traversal detection to check segments, allowing filenames like '..config.png'
packages/core/lib/src/deck_configuration.dart Enhanced path validation to split by path separators and check segments
packages/cli/lib/src/commands/build_command.dart Fixed watch mode by moving builder creation earlier and adding proper disposal
packages/builder/lib/src/deck_builder.dart Added dispose() method to clean up task resources
packages/builder/lib/src/assets/mermaid_generator.dart Fixed race condition using shared initialization future
demo/macos/Flutter/GeneratedPluginRegistrant.swift Auto-generated file updated (removed unused path_provider registration)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +70 to +71
final rawSegments = trimmed.split('/');
if (rawSegments.any((s) => s == '..' || s == '%2e%2e' || s == '%2E%2E')) {
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The new path validation logic correctly checks for '..' as a path segment rather than as a substring. However, there are no test cases covering the new behavior that allows filenames like '..config.png' or 'foo..bar.txt'. Consider adding test coverage for these edge cases to ensure the fix works as intended and to prevent regressions.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +32
final segments = pathValue.split(RegExp(r'[/\\]'));
if (segments.contains('..')) {
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The path validation logic has been updated to check for '..' as a path segment rather than as a substring. However, there are no test cases in the test file that verify this new behavior works correctly for filenames containing '..' (like '..config.png' or 'foo..bar.txt'). Consider adding test coverage to ensure this fix works as intended.

Copilot uses AI. Check for mistakes.
// Start initialization and store the future for concurrent callers
_browserInitFuture = _launchBrowser();
try {
_browser = await _browserInitFuture!;
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

After successful browser initialization, the _browserInitFuture should be cleared (set to null) to indicate initialization is complete. While this doesn't cause a bug (since the _browser != null check returns early), it's cleaner to clear the future once initialization succeeds. Add _browserInitFuture = null; after line 279.

Suggested change
_browser = await _browserInitFuture!;
_browser = await _browserInitFuture!;
_browserInitFuture = null;

Copilot uses AI. Check for mistakes.
Comment on lines +71 to 72
if (rawSegments.any((s) => s == '..' || s == '%2e%2e' || s == '%2E%2E')) {
throw FormatException('Path traversal detected');
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The updated path traversal check on trimmed only rejects segments equal to '..', '%2e%2e' or '%2E%2E', which leaves exploitable gaps for encoded traversal patterns like '..%2F', '%2e%2F' or mixed-case/partial encodings that become '../' after percent-decoding. Because UriValidator is explicitly used for untrusted markdown, an attacker can craft a file: or relative URI using these encodings to bypass this check and cause the app to load images from arbitrary paths (e.g. sensitive files) on the viewer's filesystem. To harden this, normalize/percent-decode each non-network path segment before validation and reject any segment that is or becomes '..' (including encoded or partially encoded variants), or use a canonicalized filesystem path and ensure it stays within an allowed base directory.

Suggested change
if (rawSegments.any((s) => s == '..' || s == '%2e%2e' || s == '%2E%2E')) {
throw FormatException('Path traversal detected');
for (final segment in rawSegments) {
// Percent-decode each segment and look for decoded ".." components,
// including cases like "..%2F" that become "../" after decoding.
final decoded = Uri.decodeComponent(segment);
final decodedParts = decoded.split(RegExp(r'[\/\\]'));
if (decodedParts.any((part) => part == '..')) {
throw FormatException('Path traversal detected');
}

Copilot uses AI. Check for mistakes.
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.

3 participants