Skip to content

Commit 4990b32

Browse files
committed
Merge branch 'main' into more-details-on-invalid-api
# Conflicts: # lib/index.js
2 parents 1d45c50 + 9e2d461 commit 4990b32

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+11008
-63770
lines changed

.eslintrc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# https://jstools.dev/eslint-config/
44

55
root: true
6-
extends: "@jsdevtools"
6+
extends: "./eslint-config/lib/index.js"
77
env:
88
node: true
99
browser: true

.github/FUNDING.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# These are supported funding model platforms
2+
3+
github: philsturgeon

.github/workflows/CI-CD.yaml

Lines changed: 18 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,33 @@
66
name: CI-CD
77

88
on:
9-
push:
109
pull_request:
11-
schedule:
12-
- cron: "0 0 1 * *"
10+
push:
11+
branches: [main]
1312

1413
jobs:
1514
node_tests:
1615
name: Node ${{ matrix.node }} on ${{ matrix.os }}
1716
runs-on: ${{ matrix.os }}
1817
timeout-minutes: 10
1918
strategy:
20-
fail-fast: true
2119
matrix:
2220
os:
2321
- ubuntu-latest
2422
- macos-latest
2523
- windows-latest
2624
node:
27-
- 10
28-
- 12
29-
- 14
25+
- 20
3026

3127
steps:
3228
- name: Checkout source
33-
uses: actions/checkout@v2
29+
uses: actions/checkout@v3
3430

3531
- name: Install Node ${{ matrix.node }}
36-
uses: actions/setup-node@v1
32+
uses: actions/setup-node@v3
3733
with:
3834
node-version: ${{ matrix.node }}
35+
cache: "npm"
3936

4037
- name: Install dependencies
4138
run: npm ci
@@ -55,54 +52,12 @@ jobs:
5552
github-token: ${{ secrets.GITHUB_TOKEN }}
5653
parallel: true
5754

58-
browser_tests:
59-
name: Browser Tests
60-
runs-on: ${{ matrix.os }}
61-
timeout-minutes: 10
62-
strategy:
63-
fail-fast: true
64-
matrix:
65-
os:
66-
- ubuntu-latest # Chrome, Firefox, Safari (via SauceLabs), Edge (via SauceLabs)
67-
- windows-latest # Internet Explorer
68-
69-
steps:
70-
- name: Checkout source
71-
uses: actions/checkout@v2
72-
73-
- name: Install Node
74-
uses: actions/setup-node@v1
75-
with:
76-
node-version: 12
77-
78-
- name: Install dependencies
79-
run: npm ci
80-
81-
- name: Run tests
82-
run: npm run coverage:browser
83-
env:
84-
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
85-
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
86-
87-
- name: Combine code coverage data into a single file
88-
shell: bash
89-
run: |
90-
ls -Rlh coverage/*/lcov.info
91-
cat coverage/*/lcov.info > ./coverage/lcov.info
92-
93-
- name: Send code coverage results to Coveralls
94-
uses: coverallsapp/github-action@v1.1.0
95-
with:
96-
github-token: ${{ secrets.GITHUB_TOKEN }}
97-
parallel: true
98-
9955
coverage:
10056
name: Code Coverage
10157
runs-on: ubuntu-latest
10258
timeout-minutes: 10
10359
needs:
10460
- node_tests
105-
- browser_tests
10661
steps:
10762
- name: Let Coveralls know that all tests have finished
10863
uses: coverallsapp/github-action@v1.1.0
@@ -117,22 +72,24 @@ jobs:
11772
timeout-minutes: 10
11873
needs:
11974
- node_tests
120-
- browser_tests
12175

12276
steps:
12377
- name: Checkout source
124-
uses: actions/checkout@v2
78+
uses: actions/checkout@v4
12579

12680
- name: Install Node
127-
uses: actions/setup-node@v1
81+
uses: actions/setup-node@v4
82+
with:
83+
node-version: 20
84+
cache: "npm"
12885

12986
- name: Install dependencies
13087
run: npm ci
13188

13289
- name: Publish to NPM
133-
uses: JS-DevTools/npm-publish@v1
134-
with:
135-
token: ${{ secrets.NPM_TOKEN }}
90+
run: npm publish --provenance --access public
91+
env:
92+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
13693

13794
- name: Prepare the non-scoped packaged
13895
run: |
@@ -141,7 +98,7 @@ jobs:
14198
sed -i "s/X.X.X/${VERSION}/g" dist/package.json
14299
143100
- name: Publish the non-scoped package to NPM
144-
uses: JS-DevTools/npm-publish@v1
145-
with:
146-
token: ${{ secrets.NPM_TOKEN }}
147-
package: dist/package.json
101+
run: npm publish --provenance --access public
102+
working-directory: dist
103+
env:
104+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ const SwaggerParser = require("@apidevtools/swagger-parser");
8888
When using a transpiler such as [Babel](https://babeljs.io/) or [TypeScript](https://www.typescriptlang.org/), or a bundler such as [Webpack](https://webpack.js.org/) or [Rollup](https://rollupjs.org/), you can use **ECMAScript modules** syntax instead:
8989

9090
```javascript
91-
import SwaggerParser from "@apidevtools/swagger-parser";
91+
import * as SwaggerParser from '@apidevtools/swagger-parser';
9292
```
9393

9494

@@ -106,6 +106,10 @@ API Documentation
106106
Full API documentation is available [right here](https://apitools.dev/swagger-parser/docs/)
107107

108108

109+
Security
110+
--------------------------
111+
The library, by default, attempts to resolve any files referenced using `$ref`, without considering file extensions or the location of the files. This can result in Local File Inclusion (LFI), thus, potentially sensitive information disclosure. Developers must be cautious when working with documents from untrusted sources. See [here](SECURITY.md) for more details and information on how to mitigate LFI.
112+
109113

110114
Contributing
111115
--------------------------
@@ -126,7 +130,8 @@ To build/test the project locally on your computer:
126130
4. __Run the tests__<br>
127131
`npm test`
128132

129-
133+
5. __Check the code coverage__<br>
134+
`npm run coverage`
130135

131136
License
132137
--------------------------
@@ -143,5 +148,3 @@ Thanks to these awesome companies for their support of Open Source developers
143148
[![GitHub](https://apitools.dev/img/badges/github.svg)](https://github.com/open-source)
144149
[![NPM](https://apitools.dev/img/badges/npm.svg)](https://www.npmjs.com/)
145150
[![Coveralls](https://apitools.dev/img/badges/coveralls.svg)](https://coveralls.io)
146-
[![Travis CI](https://apitools.dev/img/badges/travis-ci.svg)](https://travis-ci.com)
147-
[![SauceLabs](https://apitools.dev/img/badges/sauce-labs.svg)](https://saucelabs.com)

SECURITY.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Security Considerations
2+
3+
## Avoiding Local File Inclusion
4+
5+
### Default Behaviour
6+
7+
The library, by default, attempts to resolve any files pointed to by `$ref`, which can be a problem in specific scenarios, for example:
8+
9+
* A backend service uses the library, AND
10+
* The service processes OpenAPI documents from untrusted sources, AND
11+
* The service performs actual requests based on the processed OpenAPI document
12+
13+
For example, the below OpenAPI document refers to `/etc/passwd` via the `leak` property of the Pet object.
14+
15+
```yaml
16+
openapi: 3.0.2
17+
info:
18+
title: Example Document based on PetStore
19+
version: 1.0.11
20+
servers:
21+
- url: /api/v3
22+
paths:
23+
/pet:
24+
put:
25+
summary: Update an existing pet
26+
description: CHECK THE PET OBJECT leak PROPERTY!
27+
operationId: updatePet
28+
requestBody:
29+
description: Update an existent pet in the store
30+
content:
31+
application/json:
32+
schema:
33+
$ref: '#/components/schemas/Pet'
34+
required: true
35+
components:
36+
schemas:
37+
Pet:
38+
required:
39+
- name
40+
- photoUrls
41+
type: object
42+
properties:
43+
id:
44+
type: integer
45+
format: int64
46+
example: 10
47+
leak:
48+
type: string
49+
default:
50+
$ref: '/etc/passwd'
51+
name:
52+
type: string
53+
example: doggie
54+
xml:
55+
name: pet
56+
```
57+
58+
The following example uses swagger-parser to process the above document.
59+
60+
```
61+
import SwaggerParser from '@apidevtools/swagger-parser';
62+
63+
const documentSource = './document-shown-above.yml';
64+
const doc = await SwaggerParser.dereference(documentSource);
65+
console.log(doc.paths['/pet'].put.requestBody.content['application/json'].schema);
66+
```
67+
68+
A snippet of the resolved document is shown below.
69+
70+
```
71+
{
72+
required: [ 'name', 'photoUrls' ],
73+
type: 'object',
74+
properties: {
75+
id: { type: 'integer', format: 'int64', example: 10 },
76+
leak: {
77+
type: 'string',
78+
default: 'nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false root:*:0:0:System Administrator:/var/root:/bin/sh daemon:*:1:1:System Services:/var/root:/usr/bin/false _uucp:*:4:4:Unix to Unix Copy Protocol:/var/spool/uucp:/usr/sbin/uucico _taskgated:*:13:13:Task Gate Daemon:/var/empty:/usr/bin/false _networkd:*:24:24:Network Services:/var/networkd:/usr/bin/false _installassistant:*:25:25:Install Assistant:/var/empty:/usr/bin/false _lp:*
79+
```
80+
81+
You can mitigate the discussed behaviour by putting restrictions on file extensions and being mindful of the environment the service is running in. The following sections will go into more detail on these.
82+
83+
### Restrictions based on File Extension
84+
85+
You can and should configure the file resolver to only process YAML and JSON files. An example of how you can do this is shown below.
86+
87+
```
88+
const doc = await SwaggerParser.dereference(documentSource, {
89+
resolve: {
90+
file: {
91+
canRead: ['.yml', '.json']
92+
}
93+
}
94+
});
95+
```
96+
97+
As a result, any attempt to resolve files other than YAML and JSON will result in the following error.
98+
99+
```
100+
SyntaxError: Unable to resolve $ref pointer "/etc/passwd"
101+
at onError (node_modules/@apidevtools/json-schema-ref-parser/lib/parse.js:85:20) {
102+
toJSON: [Function: toJSON],
103+
[Symbol(nodejs.util.inspect.custom)]: [Function: inspect]
104+
}
105+
```
106+
107+
Configuring the file resolver this way only partially mitigates LFI. See the next section for additional considerations.
108+
109+
### Environmental Considerations
110+
111+
With the previously mentioned mitigation in place, an attacker could still craft a malicious OpenAPI document to make the library read arbitrary JSON or YAML files on the filesystem and potentially gain access to sensitive data (e.g. credentials). This is possible if:
112+
113+
* The actor knows (or successfully guesses) the location of a JSON or YAML file on the file system
114+
* The service using the library has privileges to read the file
115+
* The service using the library sends requests to the server specified in the OpenAPI document
116+
117+
You can prevent exploitation by hardening the environment in which the service is running:
118+
119+
* The service should run under its own dedicated user account
120+
* File system permissions should be configured so that the service cannot read any YAML or JSON files not owned by the service user
121+
122+
If you have any YAML or JSON files the service must have access to that may contain sensitive information, such as configuration file(s), you must take additional measures to prevent exploitation. A non-exhaustive list:
123+
124+
* You can implement your service so that it reads the configuration into memory at start time, then uses [setuid](https://nodejs.org/api/process.html#processsetuidid) and [setgid](https://nodejs.org/api/process.html#processsetgidid) to set the process' UID and GID to the ID of a user and ID of a group that has no access to the file on the filesystem
125+
* Do not store sensitive information, such as credentials, in the service configuration files

build-website.mjs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {build} from "esbuild";
2+
import {polyfillNode} from "esbuild-plugin-polyfill-node";
3+
4+
await build({
5+
entryPoints: ["online/src/js/index.js"],
6+
bundle: true,
7+
minify: true,
8+
sourcemap: 'external',
9+
target: 'chrome60',
10+
outfile: "online/js/bundle.js",
11+
plugins: [
12+
polyfillNode({
13+
// Options (optional)
14+
}),
15+
],
16+
});

0 commit comments

Comments
 (0)