OpenSCA: Analysis of Composer Dependencies

opensca

OpenSCA Community

Posted on September 19, 2023

OpenSCA: Analysis of Composer Dependencies

In this article, we will mainly introduce the principle of component composition parsing based on the Composer package manager.

Introducing Composer

Composer is a dependency management tool for PHP.

Inspired by NPM of Node.js and bundler of Ruby, the design of omposer has many similarities with these two.

The dependency management file for Composer is composer.json. Developers can specify the version range of each dependency in composer.json or use composer require /update /remove ${name} command to manage dependencies.

If there is a composer. json file in a project, you can execute the composer install command to automatically install the dependencies required for the current project and generate a composer. lock file.

The complete file structure of composer. json is as follows:

{  
    "name": "cakephp/app",  
    "type": "project",  
    "license": "MIT",  
    "require": {      
          "php": ">=7.2",      
          "cakephp/cakephp": "^4.3",      
          "cakephp/migrations": "^3.2",      
          "cakephp/plugin-installer": "^1.3",      
          "mobiledetect/mobiledetectlib": "^2.8"  
           },  
     "require-dev": {     
           "cakephp/bake": "^2.6",      
           "cakephp/cakephp-codesniffer": "^4.5",      
           "cakephp/debug_kit": "^4.5",      
           "josegonzalez/dotenv": "^3.2",      
           "phpunit/phpunit": "~8.5.0 || ^9.3"  
           },
}
Enter fullscreen mode Exit fullscreen mode

Here, name is the project name. type refers to the type of package, which includes four types: library, project, metapackage, and composer-plugin (library by default). license is the license declared by the project, which can be a string or an array of strings.

require-dev refers to the dependency used in the development or testing environment, and require refers to the dependency used in the production environment. The dependency is written as "name": "version", and the version can be specified as the exact version or a range.

Analytical Algorithm

composer.lock

composer. lock is an automatically generated file that can accurately locate the dependencies and versions used in PHP projects, so the priority is given to analyze the composer. lock file.

Structure of composer.lock is as follows:

{  
     "packages": [    
          {      
              "name": "a",      
              "version": "1.1.0",      
              "require": {          
                   "c": "1.1.*"      
                    }    
          },    
          {      
              "name": "b",      
              "version": "1.2.2",      
              "require": {          
                   "c": "^1.0.2"     
                    }    
          },    
          {       
              "name": "c",      
              "version": "1.1.2"    
                    }  
     ],  
     "packages-dev": []
}
Enter fullscreen mode Exit fullscreen mode

The packages and packages-dev fields contain all direct and indirect dependencies used by the project, and record the dependencies between components. packages represent the dependencies of the production environment, and packages-dev represent the dependencies of the development environment.

For example:

{  
    "name": "a",  
    "version": "1.1.0",  
    "require": {      
         "c": "1.1.*"  
     }
}
Enter fullscreen mode Exit fullscreen mode

● The project depends on component a in version 1.1.0, which depends on component c constrained to version 1.1. *.
● Similarly, it can be seen that the project depends on component b in version 1.2.2, and this component depends on component c with a version constraint of ^ 1.0.2.
● Moreover, neither component a nor component b is depended by any other dependencies. So both components are direct dependencies of the project.

Note:

  1. 1.1. * represents the versions in [1.1.0, 1.2.0)
  2. ^ 1.0.2 represents the versions in [1.0.2, 2.0.0)

From this, the dependency structure of the current project can be constructed:

Solid lines represent direct dependencies & dashed lines represent indirect dependencies

Figure: Example of composer.lock detection results

composer.json

composer.json is a dependency management file managed by developers, which will be parsed when the composer.lock file cannot be found.

composer.json only contains direct dependencies. During project construction, the required indirect dependencies will be downloaded from the composer repository and built into a composer.lock file. Therefore, the component dependencies of the project can be analyzed by simulating the composer construction process.

Structure of composer.json is as follows:

{  
    "name": "foo",  
    "type": "project",  
    "license": "MIT",  
    "require": {
        "a": "^1.1.0",
        "b": "^1.2.0",  
     },  
    "require-dev": {},
}
Enter fullscreen mode Exit fullscreen mode

require is the direct dependency actually used in the project, and require-dev is the direct dependency used during project development.

For example:

''a'': ''^ 1.1.0'' represents component a with a dependency version constraint of ^1.1.0.
''b'': ''^ 1.2.0'' represents component b with a dependency version constraint of ^1.2.0.

At this point of analysis, we can summarize the dependency relationship as shown in the following figure:

Solid lines represent direct dependencies

Through this dependency relationship, direct dependencies and the version range of the components are visible, but the specific version of the dependency cannot be determined.

In the absence of a composer.lock file, in order to further obtain accurate versions of dependencies and indirect dependencies, it is necessary to download detailed information of the corresponding components from the composer repository.

For example, the detailed information structure of component a is:

{
  "packages": {
    "a": [
      {
        "version": "1.0.1",
        "require": {
        "c": "^1.0.0"
        }
      }, 
      { 
         "version": "1.1.0", 
         "require": { 
         "c": "^1.1.0" 
          }      
      }    
     ]  
  }
}
Enter fullscreen mode Exit fullscreen mode

The packages field represents the mapping of component and version information, while the require field represents the dependency information of the component.

For this example, the constraint for component a is ^1.1.0, which requires a version in [1.1.0, 2.0.0), so version 1.1.0 is chosen.

Therefore, the component dependency structure becomes:

Image description

By hierarchical analyzing in this way, the dependency information of the entire project can be obtained.

Figure: Example of composer.json detection results

💖 💪 🙅 🚩
opensca
OpenSCA Community

Posted on September 19, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related