Skip to content

Conversation

@isahann
Copy link

@isahann isahann commented May 7, 2024

Just upgraded to the latest version of gh-pages available at the moment.
After upgrading, removed the node_modules, package-lock.json, and then ran a fresh npm i.
Building with npm build runs just fine. And after fixing a single test, npm test (building first) passes all tests too.

Please feel free to request any changes.

@isahann isahann changed the title Fixing critical vulnerabilities on 2.0.0-beta.2, caused by [email protected] #185 Fixing critical vulnerabilities on 2.0.0-beta.2, caused by [email protected] May 7, 2024
@fmalcher
Copy link
Member

fmalcher commented May 8, 2024

Thank you so much, Isahann! @JohannesHoppe will review and merge this one.

@JohannesHoppe
Copy link
Member

JohannesHoppe commented May 8, 2024

@isahann Thanks for starting this PR! 😃 🙏 Do you think you have the resources to figure out how we can address the breaking change?

@isahann
Copy link
Author

isahann commented May 8, 2024

@isahann Thanks for starting this PR! 😃 🙏 Do you think you have the resources to figure out how we can address the breaking change?

I've just downloaded the repo, haven't checked much of it besides the package.json and the breaking test.... But I'll try to take a better look into it...

@isahann isahann requested a review from JohannesHoppe May 8, 2024 14:10
@JohannesHoppe
Copy link
Member

We have holidays in Germany. I will run all tests on Monday! 👍

@musicEnfanthen
Copy link

@JohannesHoppe Any updates from the Monday tests? Would be awesome to see this critical vulnerability fixed.

@davayd
Copy link

davayd commented Jun 23, 2024

Excuse me, just commented to up the topic

@kayvanbree
Copy link

Any updates on this?

@mpellerin42
Copy link

I'm also looking forward this new version to be available, any update on this PR?

@namdhevTW
Copy link

Looking forward to merge this PR - folks. Current version is causing havoc with the critical vulnerabilities

@victor-enogwe
Copy link

👀

@isahann
Copy link
Author

isahann commented Apr 12, 2025

@JohannesHoppe any updates on this? I've recently updated to v2.0.3, but I see that it's still using [email protected]...

@tim-band
Copy link

Any chance of movement? Would you like any help?

@BGBRWR
Copy link

BGBRWR commented Aug 13, 2025

This PR addressing a critical vulnerability has been open for over a year. @JohannesHoppe, could you please confirm whether this repository is still being maintained? My team needs to decide whether to continue relying on this package or migrate away in order to resolve the issue.

@JohannesHoppe
Copy link
Member

Hi @BGBRWR,

thank you very much for your patience, and sorry for the long delay in responding - I’ve been on a long holiday and just got back.

The main issue with PR #186 is that it bumps gh-pages from v3 to v6. That introduces some tricky changes, especially because gh-pages updated its dependency on commander. Unfortunately, that results in a different version of commander than the one we’re currently using. I tried to update this in the past, but commander itself introduced breaking changes in how values are parsed from the command line. From what I remember, it opens the door to all kinds of regressions.

Another challenge is that no tests are failing with this PR, which is actually a sign that we don’t have test coverage for this area. Until now we’ve just assumed infrastructure like commander wouldn’t change in breaking ways.

That leaves us with three possible paths forward:

  1. Upgrade to gh-pages v6 and test everything thoroughly (high risk, since ~66k repos depend on angular-cli-ghpages).
  2. Fork the current gh-pages version and only patch security-relevant dependencies, without updating commander.
  3. Copy the best parts of gh-pages into our repo (not very polite, but would make us independent).

Each option is quite intensive. To make progress, I’ll start by increasing the test coverage this weekend, so we can document the current status quo. That way we’ll have a foundation for moving forward.

Thanks again for your patience and understanding!

@JohannesHoppe
Copy link
Member

Short Update: Analysis of Breaking Changes

Thank you all for your patience! I've completed a first analysis of the dependency upgrades and discovered several critical issues that need careful handling.

Current versions:

gh-pages: ^3.1.0 → ^6.1.1 (3 major versions)
commander: ^3.0.0 → ^14.0.2 (11 major versions)

1. Critical Finding: gh-pages Promise Bug (FIXED in v5.0.0)

The Problem We've Been Working Around:

In gh-pages v3.1.0, the publish() method has a critical bug where early error cases don't return a promise, causing errors to be silently swallowed if you use
await.

Evidence:

try {
  if (!fs.statSync(basePath).isDirectory()) {
    done(new Error('The "base" option must be an existing directory'));
    return;  // ❌ NO PROMISE RETURNED!
  }
} catch (err) {
  done(err);
  return;  // ❌ NO PROMISE RETURNED!
}

Our Workaround:
We use a callback-based approach instead of promises - see
https://github.com/angular-schule/angular-cli-ghpages/blob/v2.0.3/src/engine/engine.ts#L323-L333:

// do NOT (!!) await ghPages.publish,
// the promise is implemented in such a way that it always succeeds – even on errors!
return new Promise((resolve, reject) => {
  ghPages.publish(dir, options, error => {
    if (error) {
      return reject(error);
    }
    resolve(undefined);
  });
});

Fixed in v5.0.0+:

Impact: Our callback workaround should still work in v6, but we need intensive testing of error cases after upgrade and remove the workaround if possible.

2. Critical Finding: CNAME and .nojekyll File Creation (NEW in v6.1.0)

The Change:

gh-pages v6.1.0 added native support for creating CNAME and .nojekyll files, which we've been creating manually since the beginning.

Evidence:

.then((git) => {
  if (options.nojekyll) {
    log('Creating .nojekyll');
    fs.createFileSync(path.join(git.cwd, '.nojekyll'));
  }
  if (options.cname) {
    log('Creating CNAME for %s', options.cname);
    fs.writeFileSync(path.join(git.cwd, 'CNAME'), options.cname);
  }
  // ...
})

Our Current Implementation:
We create these files in https://github.com/angular-schule/angular-cli-ghpages/blob/v2.0.3/src/engine/engine.ts#L232-L284 BEFORE calling gh-pages.

Impact:

  • Files will be created twice (ours in dist/, theirs in cache)
  • No conflict expected (hopefully), but inefficient
  • Remove our code if possible and if it's backwards compatible

3. Critical Finding: commander Boolean --no- Options Default Handling (v9)

THE MOST CRITICAL ISSUE!

Commander v9 completely changed how boolean options with --no- prefix handle default values.

Evidence:

Our Broken Code:

https://github.com/angular-schule/angular-cli-ghpages/blob/v2.0.3/src/angular-cli-ghpages#L56-L67

.option('-T, --no-dotfiles', 'Includes dotfiles by default...') 
.option('--no-notfound', 'By default a 404.html file is created...')
.option('--no-nojekyll', 'By default a .nojekyll file is created...')

The Problem:

Our defaults are in https://github.com/angular-schule/angular-cli-ghpages/blob/v2.0.3/src/engine/defaults.ts#L8-L10:

dotfiles: true,   // Default should be true
notfound: true,   // Default should be true
nojekyll: true,   // Default should be true

But commander options DON'T have these defaults specified!

  • v3 behavior: --no-dotfiles automatically sets dotfiles: false, default is implicitly true
  • v9+ behavior: Default value MUST be explicitly provided or it's undefined

Impact:

  • With v3: Works by old default behaviour (commander auto-handles --no- prefix magic)
  • With v9+: Defaults will be undefined instead of true!
  • This will BREAK existing deployments that rely on default behavior (creating .nojekyll, 404.html)

Fix Required:

// Option 1: Pass defaults explicitly
.option('-T, --no-dotfiles', 'description', defaults.dotfiles)
.option('--no-notfound', 'description', defaults.notfound)
.option('--no-nojekyll', 'description', defaults.nojekyll)

4. Critical Finding: commander v9: Option Property Casing Change

Problem:
In commander v9, the property name for accessing option values changed to match the exact casing of the flag.

  • Before v9: A flag -n, --name would be accessed as opts().Name (capitalized)
  • After v9: The same flag is accessed as opts().name (lowercase, matching the flag)

Our Code Impact:
We define flags as: -d, --dir, -r, --repo, -m, --message, -b, --branch, -n, --name, -e, --email, -c, --cname, -a, --add

We access them as: commander.dir, commander.repo, commander.name, etc. (lowercase)

User Impact - BREAKING:
If users were passing options in a way that relied on the old casing behavior, their scripts will break:

# This might have worked in v3 (depending on internal behavior):
angular-cli-ghpages --Dir=dist --Repo=https://...

# v9+: Only lowercase works:
angular-cli-ghpages --dir=dist --repo=https://...

Our Risk:

  • Our internal code uses lowercase, so we're safe
  • User scripts that use different casing will break
  • This is a silent breaking change - no error, just wrong values

Cannot prevent or fix - this is commander's new behavior! (So do we really want to upgrade, see below!)

5. Critical Finding: commander v9: Excess Arguments Error

Problem:
Commander v9 made allowExcessArguments: false the default, meaning the CLI now throws an error if users provide more arguments than expected. see tj/commander.js#1409

  • Before v9: Extra arguments were silently ignored
  • After v9: Extra arguments cause the program to exit with an error

User Impact - BREAKING:
Users who accidentally (or intentionally) passed extra arguments will now get errors:

# Worked in v3 (extra-arg ignored):
angular-cli-ghpages --dir=dist extra-arg

# v9+: Errors out:
angular-cli-ghpages --dir=dist extra-arg
# Error: unexpected argument 'extra-arg'

We could explicitly set allowExcessArguments: true to restore v3 behaviour:

commander
  .allowExcessArguments(true)  // Restore v3 behaviour
  .option(...)

Minor Breaking Change: commander Default Export Removed (v12)

The Issue:

Our standalone CLI uses the default export which was removed in commander v12.
This is not an issue, we can just update the import.

Evidence:

To many breaking changes in commander!

I must admit, I'm quite concerned about the extent of breaking changes in commander across major versions. The fundamental changes to boolean option handling, default value behaviour, and potentially more (??) represent significant shifts in the library's API contract. Given that we use only a small subset of commander's features (basic option parsing for our standalone CLI), it may be worth considering forking commander v3, stripping it down to just what we need,
and maintaining a minimal, stable version internally. This would give us long-term stability without the risk of future breaking changes disrupting our users. In the end, including an argument parser should prevent additional work, but this has flipped, and letting an LLM write a small version of this is easier than maintaining this burden. Additionally, this minimises supply chain risk. --> Chances are high that i will just internally fork it so I never have to think about this again!

Other Small Breaking Changes (not an issue)

JohannesHoppe added a commit that referenced this pull request Nov 23, 2025
This massive update increases test coverage from 213 to 285 tests (+72 tests, +34%)
to prepare for the gh-pages v6 upgrade (PR #186). All critical paths are now tested,
prepareOptions is refactored into testable functions, and a public API is exported.

## Phase 1: Critical Test Gaps (20 new tests)

### gh-pages Error Callback Tests (4 tests)
- Add tests for the "do NOT await" error handling pattern
- Test error rejection, message preservation, and auth failures
- Verifies we never fall into gh-pages v3 silent failure trap
- File: src/engine/engine.spec.ts

### Monkeypatch Verification Tests (5 tests)
- Add first-ever tests for util.debuglog interception (was 0% coverage)
- Test gh-pages logging forwarding to Angular logger
- Test message formatting with placeholders
- Verify critical ordering (monkeypatch before gh-pages require)
- File: src/engine/engine.spec.ts

### Real Filesystem Tests (11 tests)
- Create first-ever real filesystem integration tests
- Use actual temp directories instead of mocks
- Test .nojekyll, CNAME, and 404.html file creation
- Test graceful error handling when index.html missing
- File: src/engine/engine-filesystem.spec.ts (NEW)

## Phase 2: Refactor prepareOptions (34 new tests)

### Code Refactoring
- Extract prepareOptions (157 lines) into 6 testable functions:
  1. setupMonkeypatch() - Intercept util.debuglog
  2. mapNegatedBooleans() - Transform noDotfiles → dotfiles
  3. handleUserCredentials() - Create user object or warn
  4. warnDeprecatedParameters() - Warn about deprecated noSilent
  5. appendCIMetadata() - Add CI environment metadata
  6. injectTokenIntoRepoUrl() - Inject authentication tokens
- prepareOptions() is now a clean orchestrator function
- File: src/engine/engine.ts

### Intensive Tests
- Add 34 comprehensive tests covering 100% of option transformation logic
- Test every extracted function exhaustively with all edge cases
- Covers all boolean combinations, CI environments, and token scenarios
- File: src/engine/prepare-options.spec.ts (NEW)

## Phase 3: Export Public API (18 new tests)

### Public API Exports
- Export Schema, PublishOptions, DeployUser, and other types
- Export defaults configuration object
- Export core functions (deployToGHPages, angularDeploy)
- Export advanced functions (all prepareOptions helpers)
- Users can now extend angular-cli-ghpages functionality
- File: src/public_api.ts

### Public API Tests
- Add 18 tests verifying all exports are accessible
- Test type compilation and runtime function availability
- Include example usage scenarios
- File: src/public-api.spec.ts (NEW)

## Documentation

- Add TEST_COVERAGE_PLAN.md documenting the complete plan
- Includes implementation order, success criteria, and file listing
- Documents context from PR #186 analysis

## Test Results

- Test suites: 16 → 18 (+2)
- Tests: 213 → 285 (+72, +34%)
- Pass rate: 100% (285/285)
- Zero regressions

## Breaking Changes

None - all changes are backwards compatible. The refactoring maintains
exact same behavior while adding exports and improving testability.

## Related

- Addresses PR #186 preparation (gh-pages v3 → v6 upgrade)
- Completes audit remediation priorities 7-10
- Enables future refactoring with test safety net
JohannesHoppe added a commit that referenced this pull request Nov 24, 2025
Add extensive test coverage for PR #186 breaking change analysis:

1. Commander v3 Boolean Defaults (NEW FILE)
   - pr-186-commander-defaults.spec.ts (9 tests)
   - Tests Commander v3 fork --no- option behavior
   - Covers all edge cases: single, double, triple negations
   - Documents why we forked Commander v3 (avoid v9+ breaking changes)
   - Eliminates all 'as any' violations (HARD RULE compliance)

2. gh-pages v6.1.0+ File Creation Compatibility
   - engine-filesystem.spec.ts (+3 tests)
   - Verifies we DON'T pass cname/nojekyll to gh-pages
   - Documents that gh-pages v6+ would create duplicates
   - Explains future upgrade path options

3. gh-pages Promise Bug Documentation
   - engine.gh-pages-behavior.spec.ts (documentation block)
   - Documents gh-pages v3.1.0 early error promise bug
   - Explains our callback workaround in engine.ts
   - Notes fix in v5.0.0+ and upgrade implications

Test Results:
- 354 tests passing (consolidated redundant tests)
- Zero 'as any' violations
- Complete edge case coverage
- Production-ready for external audit

Related: PR #186 (gh-pages v3→v6 & commander v3→v14 analysis)
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.