Search

Best Practices

Content

Managing Dependencies

Projects using CAP need to manage dependencies to the respective tools and libraries in their package.json and/or pom.xml respectively. Follow the guidelines below to make sure that you consume the latest fixes and avoid vulnerabilities and version incompatibilities. These guidelines apply to you as a consumer of reuse packages as well as a provider of such reuse packages.

Always Use the Latest Minor Releases → for Example, ^3.1.0

This applies to both, @sap packages as well as open source ones. It makes sure that your projects receive important fixes during development. It also makes sure that bundles have a minimal footprint by leveraging npm’s dedupe feature.

Example:

"dependencies": {
  "@sap/cds": "^3.1.0",
  "@sap/some-reuse-package": "^1.1.2",
  "express": "^4.16.4"
}

We recommend using the caret form, that is, ^1.0.2 to add your dependencies, which are also the default for npm install, as that clearly captures the minimum patch version.

We also recommend running npm update regularly and frequently during development to ensure that you receive the latest fixes.

Keep Open Ranges When Publishing for Reuse

Let’s explain this by looking at counter examples.

Let’s assume that you’ve developed a reuse package that others can use in their projects, and you also use a reuse package. For whatever reason, you decided to violate the previous rules and use exact dependencies in your package.json as follows:

"name": "@sap/your-reuse-package",
"version": "1.1.2",
"dependencies": {
  "@sap/cds": "3.0.3",
  "@sap/foundation": "2.0.1",
  "express": "4.16.3"
}

The effect would be as follows:

  1. Consuming projects would get duplicate versions of each package that they also use directly, for example, @sap/cds, @sap/foundation, and express.
  2. Consuming projects wouldn’t receive important fixes for the packages used in your implementations unless you also provide an update.
  3. It wouldn’t be possible to reuse CDS models from common reuse packages (for example, would already fail for @sap/cds/common).

Therefore, the rules when publishing packages for reuse are:

  • Keep the open ranges in your package.json (just don’t touch them).
  • Do an npm update before publishing and test thoroughly (→ ideally automated in your CI/CD pipeline).
  • Do the vulnerability checks for your software and all open-source software used by you or by packages you used (→ see below).
  • Don’t do npm shrinkwrap → see also npm’s docs: “It’s discouraged for library authors to publish this file, …”

If both your package and a consuming package reuse the same CDS models, loading those models would fail because it’s impossible to automatically merge the two versions, nor is it possible to load two independent versions. The reason for this is that it’s reusing models that share the same single definitions.

Shrinkwrap When Releasing to End Consumers

When releasing a service or an application to end consumers, use npm shrinkwrap to ensure frozen dependencies. This allows you to guarantee that it works correctly as it did the last time you tested it and checked it for vulnerabilities.

Overall, the process for your release should include these steps:

npm update      # ensure you got latest fixes
npm dedupe      # ensure all is in shape
npm shrinkwrap  # freeze dependencies
# conduct all test and vulnerability checks

The npm-shrinkwrap.json file in your project root freezes all dependencies and is shipped and deployed with your package. Subsequent npm installs, such as by users or by cloud deployers or build packs, always get the same versions, which you checked upon your release.

This ensures that the deployed tool/service/app doesn’t receive new vulnerabilities, for example, through updated open source packages, without you being able to apply the necessary tests as prescribed by our security standards.

Minimize Usage of Open Source Packages

This rule for keeping open ranges for dependencies during development, as well as when publishing for reuse, also applies for open source packages.

Because open source packages are less reliable with respect to vulnerability checks, this means that end-of-chain projects have to ensure respective checks for all the open source packages they use directly, as well as those they ‘inherit’ transitively from reuse packages.

So, please always take into account these rules:

  • When releasing to end consumers, you always have to conduct vulnerability checks for all open source packages that you used directly or transitively.

  • As a provider of reuse packages you should minimize the usage of open source packages to a reasonable minimum.

Q: Why not freeze open source dependencies when releasing for reuse?

A: Because that would only affect directly consumed packages, while packages from transitive dependencies would still reach your consumers.

A good approach is to also provide certain features in combination with third-party packages, but to keep them, and hence the dependencies, optional; for example, express.js does this.

Upgrade to Latest Majors as Soon as Possible

As providers of evolving SDKs we provide major feature updates, enhancements, and improvements in 6-12 month release cycles. These updates come with an increment of major release numbers.

At the same time, we can’t maintain and support unlimited numbers of branches with fixes. The following rules apply:

  • Fixes and nonbreaking enhancements are made available frequently in upstream release branches (current major)
  • Critical fixes also reach recent majors in a 2-month grace period

To make sure that you receive ongoing fixes, make sure to also adopt the latest major releases in a timely fashion in your actively maintained projects, that is, following the 6-12 month cycle.

Additional Advice

Using package-lock.json — To ensure reproducible setups with your teammates in a project, you might want to consider using package-lock.json as recommended by npm.

Note that this is in line with the guidelines and previous recommendations. Use npm update frequently during development and check in the updated package-lock.json file subsequently to share the updated setup again in your project.

Securing Your Application

To keep builds as small as possible, the Node.js runtime doesn’t bring any potentially unnecessary dependencies and, hence, doesn’t automatically mount any express middlewares, such as the popular helmet and csurf.

However, application developers can easily mount custom or best-practice express middlewares using the bootstrapping mechanism.

Example:

// local ./server.js
const cds = require('@sap/cds')
const helmet = require('helmet')

cds.on('bootstrap', (app) => {
  app.use(helmet())
})

module.exports = cds.server // > delegate to default server.js

Please consult sources such as Express’ Production Best Practices: Security documentation for state of the art application security.

Content Security Policy (CSP)

Creating a Content Security Policy (CSP) is a major building block in securing your web application.

helmet provides a default policy out-of-the-box that you can also customize as follows:

cds.on('bootstrap', (app) => {
  app.use(helmet({
    contentSecurityPolicy: {
      directives: {
        ...helmet.contentSecurityPolicy.getDefaultDirectives(),
        // custom settings
      }
    }
  }))
})

Consult OpenUI5 Content Security Policy documentation for the list of directives that UI5 requires.

Cross-Site Request Forgery (CSRF) Token

For a UI5 (SAP Fiori / SAP Fiori Elements) application developer, CSRF token handling is transparent. There’s no need to program or to configure anything in additional. In case the server rejects the request with 403 and “X-CSRF-Token: required”, the UI sends a HEAD request to the service document to fetch a new token.

❗ The request must never be cacheable.

Learn more about CSRF tokens and SAP UI5 in the Cross-Site Scripting documentation.

On the backend side, except for handling the HEAD request mentioned previously, also the handlers for each CSRF protected method and path should be added. In the following example, the POST method is protected.

If you use SAP Fiori Elements, requests to the backend are sent as batch requests using the POST method. In this case, an arbitrary POST request should be protected.

As already mentioned, in case the server rejects because of a bad CSRF token, the response with a status 403 and a header “X-CSRF-Token: required” should be returned to the UI. For this purpose, the error handling in the following example is extended:

cds.on('bootstrap',async(app) => {
  var csrfProtection = csrf({ cookie: true })
  var parseForm = bodyParser.urlencoded({ extended: false })

  app.use(cookieParser())

  app.head('/', csrfProtection, function (req, res) {
    res.set('X-CSRF-Token', req.csrfToken())
    res.send()
  })

  app.post('*', parseForm, csrfProtection, function (req, res, next) {
    next()
  })
  
  app.use(function (err, req, res, next) {
    if (err.code !== 'EBADCSRFTOKEN') return next(err)

    res.status(403)
    res.set('X-CSRF-Token', 'required')
    res.send()
  })
})

Learn more about about backend coding in the csurf documentation.

If you’re using horizontal scaling of Node.js VMs the CSRF handling should be done at the approuter level.

Show/Hide Beta Features