Skip to content

Conversation

@spatten
Copy link
Contributor

@spatten spatten commented Nov 28, 2025

Overview

Delivers ANE-2268

The idea behind fork aliasing is that you may be using a fork of a project, but you want FOSSA to treat it as the root project it is forked from. This will allow FOSSA to get proper vulnerability reports on the project.

To do this, you define "fork aliases" in fossa-deps.yml. They look like this:

  - fork:
      type: npm
      name: my-lodash
    base:
      type: npm
      name: lodash

In this case, the user has created a fork of lodash called "my-lodash". When this project is analyzed, the CLI will find an NPM project called "my-lodash", but because of the fork alias it will translate that into "npm+lodash" before sending it to FOSSA's servers, and FOSSA will treat it as if the dependency was always "npm+lodash".

The type and name fields must be specified. You may also specify a version.

If the version is specified for the fork, then only that version will be treated as a fork and translated to the base project.

If the version is specified for the base, then the translated dependency will always have the version specified in the base. If the version is not specified for the base, then the translated dependency will have the version of the fork.

You can also add labels to fork-aliases. These work exactly like labels for the other dependency types in fossa-deps.yml.

Acceptance criteria

  • You can specify fork aliases in fossa-deps.yml, and they act as specified above
  • You can add labels to fork aliased projects
  • The feature is well documented

Testing plan

Start with a project with some dependencies. Add some fork aliases and test that they work.

I used these manifest files for testing. First is a go.mod file which uses a fork of github.com/anknown/ahocorasick:

module github.com/fossas/themis

go 1.24.0

require (
        github.com/anknown/ahocorasick v0.0.0-20190904063843-d75dbd5169c0
)

replace github.com/anknown/ahocorasick => github.com/fossas/ahocorasick v0.0.0-20190904063843-d75dbd5169c0

Without fork aliases, this will report a dependency of go+github.com/fossas/ahocorasick`.

Second, a package.json that has a dependency called my-lodash:

{
  "name": "fork-alias-test",
  "version": "1.0.0",
  "description": "Testing package that has a fork alias",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "my-lodash": "spatten/lodash#4.17.21"
  }
}

and the following yarn.lock file that shows a dependency of my-lodash:

# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!

__metadata:
  version: 8
  cacheKey: 10c0

"fork-alias-test@workspace:.":
  version: 0.0.0-use.local
  resolution: "fork-alias-test@workspace:."
  dependencies:
    my-lodash: "npm:4.17.21"
  languageName: unknown
  linkType: soft

"my-lodash@npm:4.17.21":
  version: 4.17.21
  resolution: "my-lodash@npm:4.17.21"
  checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c
  languageName: node
  linkType: hard

Without any fork aliases, you will see dependencies of go+github.com/fossas/ahocorasick$d75dbd5169c0 and npm+my-lodash$4.17.21

fossa analyze --output | jq

Now add a fosas-deps.yml with some fork aliases:

fork-aliases:
  - fork:
      type: npm
      name: my-lodash
    base:
      type: npm
      name: lodash
  - fork:
      type: go
      name: github.com/fossas/ahocorasick
    base:
      type: go
      name: github.com/anknown/ahocorasick

Compile fossa-dev on this branch:

make install-dev

Run fossa-dev analyze --output. Note that the locators have been translated from npm+my-lodash to npm+lodash and go+github.com/fossas/ahocorasick to go+github.com/anknown/ahocorasick.

Do it again without the --output flag and check that the data posted to Core is correct too. I did this using echotraffic.

go run ./main.go
fossa-dev analyze -e http://localhost:3000

and checking the body POSTed to /api/builds/custom. I also looked at the dependencies found on production:

CleanShot 2025-11-28 at 15 42 56@2x

Play around with the versions. Check that if you set a version in the fork, it only translates that version. Check that if you set a version in the base, it always translates to that version.

Also, add some labels, like this:

fork-aliases:
  - fork:
      type: npm
      name: my-lodash
    base:
      type: npm
      name: lodash
    labels:
    - label: fork-alias-testing
      scope: org
  - fork:
      type: go
      name: github.com/fossas/ahocorasick
    base:
      type: git
      name: github.com/anknown/ahocorasick
      version: 1.0.0
    labels:
    - label: fork-alias-testing-2
      scope: org
CleanShot 2025-11-28 at 16 01 18@2x

Note that to get the label to show up properly on the ahocorasick dependency, I had to make it translate to a git dependency and not a go dependnecy. This is how labels work, and nothing to do with this PR. The go dependency will get resolved to a git locator, and the label will only be shown for the resolved locator.

Risks

Highlight any areas that you're unsure of, want feedback on, or want reviewers to pay particular attention to.

Example: I'm not sure I did X correctly, can reviewers please double-check that for me?

Metrics

Is this change something that can or should be tracked? If so, can we do it today? And how? If its easy, do it

References

Add links to any referenced GitHub issues, Zendesk tickets, Jira tickets, Slack threads, etc.

Example:

Checklist

  • I added tests for this PR's change (or explained in the PR description why tests don't make sense).
  • If this PR introduced a user-visible change, I added documentation into docs/.
  • If this PR added docs, I added links as appropriate to the user manual's ToC in docs/README.ms and gave consideration to how discoverable or not my documentation is.
  • If this change is externally visible, I updated Changelog.md. If this PR did not mark a release, I added my changes into an ## Unreleased section at the top.
  • If I made changes to .fossa.yml or fossa-deps.{json.yml}, I updated docs/references/files/*.schema.json AND I have updated example files used by fossa init command. You may also need to update these if you have added/removed new dependency type (e.g. pip) or analysis target type (e.g. poetry).
  • If I made changes to a subcommand's options, I updated docs/references/subcommands/<subcommand>.md.

@spatten spatten changed the title Fork aliasing no locators Fork aliasing Dec 1, 2025
@spatten spatten force-pushed the fork-aliasing-no-locators branch from 4a57dc1 to 7d02588 Compare December 1, 2025 21:20
@spatten spatten requested a review from csasarak December 1, 2025 22:37
@spatten spatten marked this pull request as ready for review December 1, 2025 23:37
@spatten spatten requested a review from a team as a code owner December 1, 2025 23:37

**Version Matching rules:**
- If `fork` version is specified, only that exact version will be translated
- If `fork` version is not specified, any version will match
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this mean that every vulnerability for any version of the upstream package gets reported?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It means that we will treat it as a fork alias for any version. Let me re-write that to be more clear.

collectForkAliasLabels = Map.fromListWith (++) . mapMaybe forkAliasToLabel
where
forkAliasToLabel :: ForkAlias -> Maybe (Text, [ProvidedPackageLabel])
forkAliasToLabel ForkAlias{..} =
Copy link
Contributor

Choose a reason for hiding this comment

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

[nit/Optional] I've been moving away from record wild-cards to using explicit names or RecordDotSyntax. Personally, I prefer the explicitness of both over having to figure out which bindings come from the match. LSP should offer a quick action to add in the items you actually used.

RecordDotSyntax allows a syntax like in Rust: forkAlias.forkAliasBase.

Comment on lines 98 to 106
- `fork`: The fork dependency entry that should be aliased to the base dependency. (Required)
- `type`: Type of the fork dependency. (Required)
- `name`: Name of the fork dependency. (Required)
- `version`: Version of the fork dependency. (Optional)
- `base`: The base/original dependency entry that your fork should be aliased to. (Required)
- `type`: Type of the base dependency. (Required)
- `name`: Name of the base dependency. (Required)
- `version`: Version of the base dependency. (Optional)
- `labels`: An optional list of labels to be added to the fork alias.
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these keys meant to match the yaml, or be a list of definitions?

This isn't blocking, but when I tested against prod these looked like normal deps. If you haven't already done it, I think we should consider adding a default fork-alias label to every instance of a fork-alias dependency. Without that, I think we or the customer would have to correlate deps between the UI and fossa-deps.yml in order to get the full picture that these deps aren't "normal".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They are normal deps, and Core has no idea that they are fork-aliases. I like the suggestion, but it would add a significant amount of work to do it, so I'm going to leave it as is for now.

I'll add a note that it will add the label to all versions of the dependency in the project, and that we suggest using the project or revision scope so that it doesn't add the label across the org.

- `fork`: The fork dependency entry that should be aliased to the base dependency. (Required)
- `type`: Type of the fork dependency. (Required)
- `name`: Name of the fork dependency. (Required)
- `version`: Version of the fork dependency. (Optional)
Copy link
Contributor

Choose a reason for hiding this comment

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

For a "fork" dependency that just means this is the version we report - right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the version is there, then we will only translate that version. If it's not there, we will translate any version of the package. I've added a note to see the "version matching rules" below for more details

spec = do
describe "mkForkAliasMap" $ do
it "should create a map keyed by project locator" $ do
let fork = ForkAliasEntry CargoType "my-serde" (Just "1.0.0")
Copy link
Contributor

Choose a reason for hiding this comment

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

[nit] I've come to dislike using record constructors positionally when there are more than 1 or 2 items. I think it's easier to understand what each value is when the label is there. If you agree, the language server can do the heavy work for you: https://teamfossa.slack.com/archives/CU3EPRS11/p1751318795964299

Comment on lines 422 to 428
data ForkAliasEntry = ForkAliasEntry
{ forkAliasEntryType :: DepType
, forkAliasEntryName :: Text
, forkAliasEntryVersion :: Maybe Text
}
deriving (Eq, Ord, Show)

Copy link
Contributor

Choose a reason for hiding this comment

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

data ForkAliasEntryKind = Base | Fork

data ForkAliasEntry (a :: ForkAliasKind) = ForkAliasEntry
  { forkAliasEntryType :: DepType
  , forkAliasEntryName :: Text
  , forkAliasEntryVersion :: Maybe Text
  }
  deriving (Eq, Ord, Show)

...


data ForkAlias = ForkAlias
  { forkAliasFork :: ForkAliasEntry 'Fork
  , forkAliasBase :: ForkAliasEntry 'Base
  , forkAliasLabels :: [ProvidedPackageLabel]
  }
  deriving (Eq, Ord, Show)

You would need the {-# LANGUAGE DataKinds #-} pragma as well.

Copy link
Contributor Author

@spatten spatten left a comment

Choose a reason for hiding this comment

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

I've clarified the documentation a bit

Comment on lines 98 to 106
- `fork`: The fork dependency entry that should be aliased to the base dependency. (Required)
- `type`: Type of the fork dependency. (Required)
- `name`: Name of the fork dependency. (Required)
- `version`: Version of the fork dependency. (Optional)
- `base`: The base/original dependency entry that your fork should be aliased to. (Required)
- `type`: Type of the base dependency. (Required)
- `name`: Name of the base dependency. (Required)
- `version`: Version of the base dependency. (Optional)
- `labels`: An optional list of labels to be added to the fork alias.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

They are normal deps, and Core has no idea that they are fork-aliases. I like the suggestion, but it would add a significant amount of work to do it, so I'm going to leave it as is for now.

I'll add a note that it will add the label to all versions of the dependency in the project, and that we suggest using the project or revision scope so that it doesn't add the label across the org.

- `fork`: The fork dependency entry that should be aliased to the base dependency. (Required)
- `type`: Type of the fork dependency. (Required)
- `name`: Name of the fork dependency. (Required)
- `version`: Version of the fork dependency. (Optional)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the version is there, then we will only translate that version. If it's not there, we will translate any version of the package. I've added a note to see the "version matching rules" below for more details


**Version Matching rules:**
- If `fork` version is specified, only that exact version will be translated
- If `fork` version is not specified, any version will match
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It means that we will treat it as a fork alias for any version. Let me re-write that to be more clear.

@spatten spatten merged commit 5116f00 into master Dec 4, 2025
19 checks passed
@spatten spatten deleted the fork-aliasing-no-locators branch December 4, 2025 21:28
@spatten spatten mentioned this pull request Dec 16, 2025
6 tasks
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