Repository: elastic/synthetics
Pull request #181 (2021-01-06 09:04:07)

feat: add seccomp profiler
This is the first draft for generating seccomp profile from any runtime environment. This approach uses the syslog generated from seccomp violations and parses these logs to create a seccomp profile in yaml format. By default it runs the synthetic agent to generate the profile but it can be configured to work for other executables.

To generate a profile use  `docker-compose up seccomp-profiler` (in `seccomp` folder)


Remaning issues:
- The rsyslog service seems to be running a bit late which creates inconsistency when generating the profile
- We should use docker syslog driver

jahtalab: @andrewvc , I've created this draft so we can discuss the approach in more detail. Please have a look and let me know what you think.
apmmachine: 
## :broken_heart: Build Failed


<!-- BUILD BADGES-->
> _the below badges are clickable and redirect to their specific view in the CI or DOCS_
[![Pipeline View](https://img.shields.io/badge/pipeline-pipeline%20-green)](https://apm-ci.elastic.co/blue/organizations/jenkins/apm-agent-rum%2Felastic-synthetics%2FPR-181/detail/PR-181/1//pipeline) [![Test View](https://img.shields.io/badge/test-test-green)](https://apm-ci.elastic.co/blue/organizations/jenkins/apm-agent-rum%2Felastic-synthetics%2FPR-181/detail/PR-181/1//tests) [![Changes](https://img.shields.io/badge/changes-changes-green)](https://apm-ci.elastic.co/blue/organizations/jenkins/apm-agent-rum%2Felastic-synthetics%2FPR-181/detail/PR-181/1//changes) [![Artifacts](https://img.shields.io/badge/artifacts-artifacts-yellow)](https://apm-ci.elastic.co/blue/organizations/jenkins/apm-agent-rum%2Felastic-synthetics%2FPR-181/detail/PR-181/1//artifacts) [![preview](https://img.shields.io/badge/docs-preview-yellowgreen)](http://synthetics_181.docs-preview.app.elstc.co/diff)

<!-- BUILD SUMMARY-->
<details><summary>Expand to view the summary</summary>
<p>

#### Build stats

* Build Cause: `Pull request #181 opened`

* Start Time: 2021-01-06T09:04:16.660+0000

* Duration: 12 min 7 sec




#### Test stats :test_tube:

| Test         | Results                         |
| ------------ | :-----------------------------: |
| Failed       | 0  |
| Passed       | 46  |
| Skipped      | 0 |
| Total        | 46   |


</p>
</details>

<!-- TEST RESULTS IF ANY-->


<!-- STEPS ERRORS IF ANY -->





### Steps errors [![2](https://img.shields.io/badge/2%20-red)](https://apm-ci.elastic.co/blue/organizations/jenkins/apm-agent-rum%2Felastic-synthetics%2FPR-181/detail/PR-181/1//pipeline)
  <details><summary>Expand to view the steps failures</summary>
  <p>

  
  
  
  
  
##### `Build Docker image`
<ul>
<li>Took 0 min 0 sec . View more details on <a href="https://apm-ci.elastic.co//blue/rest/organizations/jenkins/pipelines/apm-agent-rum/pipelines/elastic-synthetics/pipelines/PR-181/runs/1/steps/385/log/?start=0">here</a></li>

</ul>
  
  
  
  
##### `Build Docker image`
<ul>
<li>Took 0 min 0 sec . View more details on <a href="https://apm-ci.elastic.co//blue/rest/organizations/jenkins/pipelines/apm-agent-rum/pipelines/elastic-synthetics/pipelines/PR-181/runs/1/steps/456/log/?start=0">here</a></li>

</ul>
  
  </p>
  </details>



<!-- LOG OUTPUT-->
### Log output

<details><summary>Expand to view the last 100 lines of log output</summary>
<p>

```
[2021-01-06T09:16:16.871Z] npm notice 244B   dist/reporters/index.d.ts.map        
[2021-01-06T09:16:16.871Z] npm notice 1.6kB  dist/core/index.js.map               
[2021-01-06T09:16:16.871Z] npm notice 178B   dist/dsl/index.js.map                
[2021-01-06T09:16:16.871Z] npm notice 1.5kB  dist/index.js.map                    
[2021-01-06T09:16:16.871Z] npm notice 221B   dist/plugins/index.js.map            
[2021-01-06T09:16:16.871Z] npm notice 252B   dist/reporters/index.js.map          
[2021-01-06T09:16:16.871Z] npm notice 1.0kB  dist/dsl/journey.d.ts.map            
[2021-01-06T09:16:16.871Z] npm notice 776B   dist/dsl/journey.js.map              
[2021-01-06T09:16:16.871Z] npm notice 659B   dist/reporters/json.d.ts.map         
[2021-01-06T09:16:16.871Z] npm notice 3.0kB  dist/reporters/json.js.map           
[2021-01-06T09:16:16.871Z] npm notice 225B   dist/core/logger.d.ts.map            
[2021-01-06T09:16:16.871Z] npm notice 925B   dist/core/logger.js.map              
[2021-01-06T09:16:16.871Z] npm notice 827B   dist/plugins/network.d.ts.map        
[2021-01-06T09:16:16.871Z] npm notice 6.3kB  dist/plugins/network.js.map          
[2021-01-06T09:16:16.871Z] npm notice 221B   dist/parse_args.d.ts.map             
[2021-01-06T09:16:16.871Z] npm notice 1.2kB  dist/parse_args.js.map               
[2021-01-06T09:16:16.871Z] npm notice 757B   dist/plugins/performance.d.ts.map    
[2021-01-06T09:16:16.871Z] npm notice 1.0kB  dist/plugins/performance.js.map      
[2021-01-06T09:16:16.871Z] npm notice 967B   dist/plugins/plugin-manager.d.ts.map 
[2021-01-06T09:16:16.871Z] npm notice 1.9kB  dist/plugins/plugin-manager.js.map   
[2021-01-06T09:16:16.871Z] npm notice 3.2kB  dist/core/runner.d.ts.map            
[2021-01-06T09:16:16.871Z] npm notice 6.6kB  dist/core/runner.js.map              
[2021-01-06T09:16:16.871Z] npm notice 342B   dist/dsl/step.d.ts.map               
[2021-01-06T09:16:16.871Z] npm notice 328B   dist/dsl/step.js.map                 
[2021-01-06T09:16:16.871Z] npm notice 687B   dist/plugins/tracing.d.ts.map        
[2021-01-06T09:16:16.871Z] npm notice 1.5kB  dist/plugins/tracing.js.map          
[2021-01-06T09:16:16.871Z] npm notice 1.7kB  README.md                            
[2021-01-06T09:16:16.871Z] npm notice 1.6kB  dist/reporters/base.d.ts             
[2021-01-06T09:16:16.871Z] npm notice 4.3kB  src/reporters/base.ts                
[2021-01-06T09:16:16.871Z] npm notice 1.6kB  dist/plugins/browser-console.d.ts    
[2021-01-06T09:16:16.871Z] npm notice 2.2kB  src/plugins/browser-console.ts       
[2021-01-06T09:16:16.871Z] npm notice 1.2kB  dist/browser-service.d.ts            
[2021-01-06T09:16:16.871Z] npm notice 2.0kB  src/browser-service.ts               
[2021-01-06T09:16:16.871Z] npm notice 1.2kB  dist/cli.d.ts                        
[2021-01-06T09:16:16.871Z] npm notice 5.0kB  src/cli.ts                           
[2021-01-06T09:16:16.871Z] npm notice 2.7kB  dist/common_types.d.ts               
[2021-01-06T09:16:16.871Z] npm notice 2.5kB  src/common_types.ts                  
[2021-01-06T09:16:16.871Z] npm notice 2.1kB  dist/core/gatherer.d.ts              
[2021-01-06T09:16:16.871Z] npm notice 3.1kB  src/core/gatherer.ts                 
[2021-01-06T09:16:16.871Z] npm notice 3.8kB  dist/helpers.d.ts                    
[2021-01-06T09:16:16.871Z] npm notice 6.6kB  src/helpers.ts                       
[2021-01-06T09:16:16.871Z] npm notice 1.8kB  dist/core/index.d.ts                 
[2021-01-06T09:16:16.871Z] npm notice 1.2kB  dist/dsl/index.d.ts                  
[2021-01-06T09:16:16.871Z] npm notice 1.4kB  dist/index.d.ts                      
[2021-01-06T09:16:16.871Z] npm notice 1.3kB  dist/plugins/index.d.ts              
[2021-01-06T09:16:16.871Z] npm notice 1.4kB  dist/reporters/index.d.ts            
[2021-01-06T09:16:16.871Z] npm notice 2.8kB  src/core/index.ts                    
[2021-01-06T09:16:16.871Z] npm notice 1.2kB  src/dsl/index.ts                     
[2021-01-06T09:16:16.871Z] npm notice 2.7kB  src/index.ts                         
[2021-01-06T09:16:16.871Z] npm notice 1.3kB  src/plugins/index.ts                 
[2021-01-06T09:16:16.871Z] npm notice 1.3kB  src/reporters/index.ts               
[2021-01-06T09:16:16.871Z] npm notice 2.1kB  dist/dsl/journey.d.ts                
[2021-01-06T09:16:16.871Z] npm notice 2.2kB  src/dsl/journey.ts                   
[2021-01-06T09:16:16.871Z] npm notice 1.6kB  dist/reporters/json.d.ts             
[2021-01-06T09:16:16.871Z] npm notice 5.0kB  src/reporters/json.ts                
[2021-01-06T09:16:16.871Z] npm notice 1.3kB  dist/core/logger.d.ts                
[2021-01-06T09:16:16.871Z] npm notice 1.8kB  src/core/logger.ts                   
[2021-01-06T09:16:16.871Z] npm notice 2.7kB  dist/plugins/network.d.ts            
[2021-01-06T09:16:16.871Z] npm notice 8.4kB  src/plugins/network.ts               
[2021-01-06T09:16:16.871Z] npm notice 1.3kB  dist/parse_args.d.ts                 
[2021-01-06T09:16:16.871Z] npm notice 3.0kB  src/parse_args.ts                    
[2021-01-06T09:16:16.871Z] npm notice 1.8kB  dist/plugins/performance.d.ts        
[2021-01-06T09:16:16.871Z] npm notice 2.4kB  src/plugins/performance.ts           
[2021-01-06T09:16:16.871Z] npm notice 2.1kB  dist/plugins/plugin-manager.d.ts     
[2021-01-06T09:16:16.871Z] npm notice 3.4kB  src/plugins/plugin-manager.ts        
[2021-01-06T09:16:16.871Z] npm notice 4.4kB  dist/core/runner.d.ts                
[2021-01-06T09:16:16.871Z] npm notice 9.6kB  src/core/runner.ts                   
[2021-01-06T09:16:16.871Z] npm notice 1.4kB  dist/dsl/step.d.ts                   
[2021-01-06T09:16:16.871Z] npm notice 1.4kB  src/dsl/step.ts                      
[2021-01-06T09:16:16.871Z] npm notice 2.1kB  dist/plugins/tracing.d.ts            
[2021-01-06T09:16:16.871Z] npm notice 3.4kB  src/plugins/tracing.ts               
[2021-01-06T09:16:16.871Z] npm notice 71.6kB dist/tsconfig.tsbuildinfo            
[2021-01-06T09:16:16.871Z] npm notice === Tarball Details === 
[2021-01-06T09:16:16.871Z] npm notice name:          @elastic/synthetics                     
[2021-01-06T09:16:16.871Z] npm notice version:       0.0.1-alpha.8                           
[2021-01-06T09:16:16.871Z] npm notice filename:      elastic-synthetics-0.0.1-alpha.8.tgz    
[2021-01-06T09:16:16.871Z] npm notice package size:  62.6 kB                                 
[2021-01-06T09:16:16.871Z] npm notice unpacked size: 331.9 kB                                
[2021-01-06T09:16:16.871Z] npm notice shasum:        cfd5a5f792e3327a9edac5582b3982ff0a75d159
[2021-01-06T09:16:16.871Z] npm notice integrity:     sha512-VS3w41HQr0UvD[...]jGHfZpw8+JKsQ==
[2021-01-06T09:16:16.872Z] npm notice total files:   114                                     
[2021-01-06T09:16:16.872Z] npm notice 
[2021-01-06T09:16:16.872Z] elastic-synthetics-0.0.1-alpha.8.tgz
[2021-01-06T09:16:18.750Z] Running in /var/lib/jenkins/workspace/nt-rum_elastic-synthetics_PR-181/src/github.com/elastic/synthetics
[2021-01-06T09:16:18.783Z] [INFO] getVaultSecret: Getting secrets
[2021-01-06T09:16:19.038Z] Masking supported pattern matches of $VAULT_ADDR or $VAULT_ROLE_ID or $VAULT_SECRET_ID
[2021-01-06T09:16:19.902Z] + set +x
[2021-01-06T09:16:20.164Z] Login Succeeded
[2021-01-06T09:16:20.583Z] + date +%Y-%m-%d_%H:%M
[2021-01-06T09:16:20.583Z] + docker build -t docker.elastic.co/observability-ci/synthetics:7a147e1c34631e44c139677a83dbe8a8f1f1d340-7.10.0-synthetics -t docker.elastic.co/observability-ci/synthetics:PR-181-7.10.0-synthetics --build-arg STACK_VERSION=7.10.0-synthetics --label BRANCH_NAME=PR-181 --label GIT_SHA=7a147e1c34631e44c139677a83dbe8a8f1f1d340 --label TIMESTAMP=2021-01-06_09:16 .
[2021-01-06T09:16:20.583Z] Sending build context to Docker daemon  3.089MB
[2021-01-06T09:16:20.583Z] Error response from daemon: dockerfile parse error line 22: unknown instruction: /USR/SHARE/HEARTBEAT/.NPM
[2021-01-06T09:16:21.703Z] Stage "experimental" skipped due to earlier failure(s)
[2021-01-06T09:16:21.911Z] Failed in branch Matrix - STACK_VERSION = '7.10.0-synthetics'
[2021-01-06T09:16:22.222Z] Running on Jenkins in /var/lib/jenkins/workspace/nt-rum_elastic-synthetics_PR-181
[2021-01-06T09:16:22.311Z] [INFO] getVaultSecret: Getting secrets
[2021-01-06T09:16:22.562Z] Masking supported pattern matches of $VAULT_ADDR or $VAULT_ROLE_ID or $VAULT_SECRET_ID
[2021-01-06T09:16:24.339Z] + chmod 755 generate-build-data.sh
[2021-01-06T09:16:24.339Z] + ./generate-build-data.sh https://apm-ci.elastic.co/blue/rest/organizations/jenkins/pipelines/apm-agent-rum/elastic-synthetics/PR-181/ https://apm-ci.elastic.co/blue/rest/organizations/jenkins/pipelines/apm-agent-rum/elastic-synthetics/PR-181/runs/1 FAILURE 727411
[2021-01-06T09:16:24.590Z] INFO: curl https://apm-ci.elastic.co/blue/rest/organizations/jenkins/pipelines/apm-agent-rum/elastic-synthetics/PR-181/runs/1/steps/?limit=10000 -o steps-info.json

```

</p>
</details>



<!--COMMENT_GENERATED_WITH_ID_comment.id-->

Patch:
diff --git a/.gitignore b/.gitignore
index 12a443c..0e6b6f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -111,3 +111,4 @@ tmp
 __tests__/e2e/tmp/
 
 .idea/
+seccomp/build
\ No newline at end of file
diff --git a/jest.config.js b/jest.config.js
index b0356e5..308afdd 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -30,5 +30,7 @@ module.exports = {
   coverageDirectory: 'coverage',
   coverageProvider: 'v8',
   testPathIgnorePatterns: ['dist'],
-  modulePathIgnorePatterns: ['/e2e/']
+  modulePathIgnorePatterns: ['/e2e/', '/utils/'],
+  globalSetup: './__tests__/utils/jest-global-setup.ts',
+  globalTeardown: './__tests__/utils/jest-global-teardown.ts',
 };
diff --git a/package.json b/package.json
index 3cd3cad..fecfc24 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,9 @@
     "watch": "tsc -w",
     "lint": "eslint . --rulesdir utils/eslint-rules",
     "lint:fix": "npm run lint -- --fix",
-    "test": "jest",
+    "test": "npm run test:unit && npm run test:browser-service",
+    "test:unit": "jest",
+    "test:browser-service": "WSENDPOINT=ws://localhost:9322 npm run test:unit",
     "coverage": "jest --coverage",
     "docker": "./build-docker.sh"
   },
@@ -62,4 +64,4 @@
     "prettier": "^2.2.1",
     "ts-jest": "^26.4.4"
   }
-}
+}
\ No newline at end of file
diff --git a/seccomp/.dockerignore b/seccomp/.dockerignore
new file mode 100644
index 0000000..5064824
--- /dev/null
+++ b/seccomp/.dockerignore
@@ -0,0 +1,106 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript v1 declaration files
+typings/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+
+# Next.js build output
+.next
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and *not* Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+tmp
+
+# Vim temp files
+*.swp
diff --git a/seccomp/Dockerfile b/seccomp/Dockerfile
new file mode 100644
index 0000000..fc873b6
--- /dev/null
+++ b/seccomp/Dockerfile
@@ -0,0 +1,14 @@
+FROM mcr.microsoft.com/playwright
+
+USER root
+RUN apt-get update
+RUN apt-get -y install rsyslog
+
+RUN apt-get -y install golang
+RUN go get github.com/elastic/go-seccomp-bpf github.com/elastic/go-ucfg/yaml
+
+COPY ./ /home/synthetics
+WORKDIR /home/synthetics
+
+RUN npm install
+RUN npm run build
\ No newline at end of file
diff --git a/seccomp/docker-compose.yml b/seccomp/docker-compose.yml
new file mode 100644
index 0000000..e98d093
--- /dev/null
+++ b/seccomp/docker-compose.yml
@@ -0,0 +1,13 @@
+version: "3.7"
+services:
+  seccomp-profiler:
+    build:
+      context: ../
+      dockerfile: seccomp/Dockerfile
+    working_dir: /home/synthetics/seccomp
+    command: bash -c "sh /home/synthetics/seccomp/profile.sh"
+    stdin_open: true
+    tty: true
+    privileged: true
+    volumes:
+      - ./:/home/synthetics/seccomp/
diff --git a/seccomp/parse-syslog.js b/seccomp/parse-syslog.js
new file mode 100644
index 0000000..77778a9
--- /dev/null
+++ b/seccomp/parse-syslog.js
@@ -0,0 +1,78 @@
+/**
+ * MIT License
+ *
+ * Copyright (c) 2020-present, Elastic NV
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+/* eslint-disable @typescript-eslint/no-var-requires */
+const fs = require('fs');
+const syscallTable = require('./x86_64_table.json');
+
+const syslogFile = process.argv[2] || './syslog';
+
+console.log(`# Seccomp profile generated from ${syslogFile}`);
+console.log(`
+seccomp:
+  default_action: errno
+  syscalls:
+`);
+const data = fs.readFileSync(syslogFile, 'UTF-8');
+const lines = data.split(/\r?\n/);
+
+const parseLine = line => {
+  const tuples = line.split(' ');
+  const log = {};
+  tuples.forEach(tuple => {
+    const parts = tuple.split('=');
+    log[parts[0]] = parts[1];
+  });
+  return log;
+};
+
+const seccomp = {};
+
+lines.forEach(line => {
+  const { syscall, exe } = parseLine(line);
+  if (syscall && exe) {
+    if (!seccomp[exe]) {
+      seccomp[exe] = {};
+    }
+    if (!seccomp[exe][syscall]) {
+      seccomp[exe][syscall] = syscallTable[syscall] || syscall;
+    }
+  }
+});
+
+for (const key in seccomp) {
+  if (seccomp.hasOwnProperty(key)) {
+    const syscalls = seccomp[key];
+    console.log(`  - \n  # ${key}`);
+    console.log(`    action: allow`);
+    console.log(`    names:`);
+    for (const k in syscalls) {
+      if (syscalls.hasOwnProperty(k)) {
+        const syscall = syscalls[k];
+        console.log(`    - ${syscall} #${k}`);
+      }
+    }
+  }
+}
diff --git a/seccomp/profile.sh b/seccomp/profile.sh
new file mode 100644
index 0000000..816b7be
--- /dev/null
+++ b/seccomp/profile.sh
@@ -0,0 +1,16 @@
+# TODO: use docker syslog driver instead
+service rsyslog start
+# npm run build
+
+go build -o ./build/seccomp_exec seccomp_exec.go
+
+node ../dist/browser-service.js &
+tail -f /var/log/syslog > ./build/syslog &
+
+./build/seccomp_exec -policy=./seccomp_log_agent.yml node ../dist/cli.js ../examples/todos --ws-endpoint=ws://localhost:9322
+
+./build/seccomp_exec -policy=./seccomp_log_agent.yml cat ../examples/inline/sample-inline-journey.js | node ../dist/cli.js --inline --ws-endpoint=ws://localhost:9322
+
+service rsyslog stop
+
+node parse-syslog.js ./build/syslog > synthetics_agent_profile.yml
\ No newline at end of file
diff --git a/seccomp/seccomp_agent_runtime.yml b/seccomp/seccomp_agent_runtime.yml
new file mode 100644
index 0000000..f8d1f7e
--- /dev/null
+++ b/seccomp/seccomp_agent_runtime.yml
@@ -0,0 +1,32 @@
+# Seccomp profile generated from ./build/syslog
+
+seccomp:
+  default_action: errno
+  syscalls:
+  - 
+    action: allow
+    names:
+      - clock_gettime
+  -
+    # "/usr/bin/node"
+    action: allow
+    names:
+      - read #0
+      - write #1
+      - close #3
+      - mprotect #10
+      - writev #20
+      - getpid #39
+      - epoll_wait #232
+      - epoll_ctl #233
+      - openat #257
+      - statx #332
+  -
+    # "/home/synthetics/seccomp/seccomp_exec"
+    action: allow
+    names:
+      - rt_sigprocmask #14
+      - getpid #39
+      - clone #56
+      - newfstatat #262
+      - pipe2 #293
\ No newline at end of file
diff --git a/seccomp/seccomp_agent_static_analysis.yml b/seccomp/seccomp_agent_static_analysis.yml
new file mode 100644
index 0000000..ad6183a
--- /dev/null
+++ b/seccomp/seccomp_agent_static_analysis.yml
@@ -0,0 +1,105 @@
+seccomp:
+  default_action: log
+  syscalls:
+  - names:
+    - stat #4
+    - mprotect #10
+    - brk #12
+    - access #21
+    - socket #41
+    - connect #42
+    - bind #49
+    - getsockname #51
+    - gettimeofday #96
+    - getuid #102
+    - getgid #104
+    - geteuid #107
+    - getegid #108
+    - set_tid_address #218
+    - clock_gettime #228
+    - epoll_wait #232
+    - set_robust_list #273
+    - prlimit64 #302
+    - statx #332
+    action: allow
+  - names:
+    - accept
+    - accept4
+    - arch_prctl
+    - capget
+    - capset
+    - chdir
+    - chroot
+    - clone
+    - close
+    - copy_file_range
+    - dup
+    - dup2
+    - dup3
+    - epoll_create
+    - epoll_create1
+    - epoll_ctl
+    - epoll_pwait
+    - execve
+    - exit
+    - exit_group
+    - fchdir
+    - fchmod
+    - fchown
+    - fcntl
+    - fstat
+    - fsync
+    - ftruncate
+    - futex
+    - getdents64
+    - getpid
+    - getsockopt
+    - gettid
+    - ioctl
+    - kill
+    - lseek
+    - madvise
+    - mincore
+    - mlock
+    - mmap
+    - mount
+    - munmap
+    - nanosleep
+    - newfstatat
+    - openat
+    - pipe
+    - pipe2
+    - prctl
+    - pread64
+    - ptrace
+    - pwrite64
+    - read
+    - readlinkat
+    - recvfrom
+    - recvmsg
+    - rt_sigaction
+    - rt_sigprocmask
+    - rt_sigreturn
+    - sched_getaffinity
+    - sched_yield
+    - seccomp
+    - sendmsg
+    - sendto
+    - setgid
+    - setgroups
+    - setitimer
+    - setpgid
+    - setsid
+    - setsockopt
+    - setuid
+    - shutdown
+    - sigaltstack
+    - tgkill
+    - uname
+    - unshare
+    - wait4
+    - waitid
+    - write
+    - writev
+    action: allow
+
diff --git a/seccomp/seccomp_exec.go b/seccomp/seccomp_exec.go
new file mode 100644
index 0000000..e194f5c
--- /dev/null
+++ b/seccomp/seccomp_exec.go
@@ -0,0 +1,95 @@
+// Licensed to Elasticsearch B.V. under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Elasticsearch B.V. licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"os"
+	"os/exec"
+
+	"github.com/elastic/go-ucfg/yaml"
+
+	seccomp "github.com/elastic/go-seccomp-bpf"
+)
+
+var (
+	policyFile string
+	noNewPrivs bool
+)
+
+func main() {
+	flag.StringVar(&policyFile, "policy", "seccomp.yml", "seccomp policy file")
+	flag.BoolVar(&noNewPrivs, "no-new-privs", true, "set no new privs bit")
+	flag.Parse()
+
+	args := flag.Args()
+	if len(args) == 0 {
+		fmt.Fprintf(os.Stderr, "You must specify a command and args to execute.\n")
+		os.Exit(1)
+	}
+
+	policy, err := parsePolicy(policyFile)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error: %v\n", err)
+		os.Exit(1)
+	}
+
+	filter := seccomp.Filter{
+		NoNewPrivs: noNewPrivs,
+		Flag:       seccomp.FilterFlagTSync,
+		Policy:     *policy,
+	}
+
+	if err = seccomp.LoadFilter(filter); err != nil {
+		fmt.Fprintf(os.Stderr, "error loading filter: %v\n", err)
+		os.Exit(1)
+	}
+
+	// Execute the specified command
+	cmd := exec.Command(args[0], args[1:]...)
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	cmd.Stdin = os.Stdin
+
+	/*
+	 * cmd.Start doesn't apply the seccomp rules
+	 */
+	if err = cmd.Run(); err != nil {
+		fmt.Fprintf(os.Stderr, "error: %v\n", err)
+		os.Exit(1)
+	}
+}
+
+func parsePolicy(policyFile string) (*seccomp.Policy, error) {
+	conf, err := yaml.NewConfigWithFile(policyFile)
+	if err != nil {
+		return nil, err
+	}
+
+	type Config struct {
+		Seccomp seccomp.Policy
+	}
+
+	var config Config
+	if err = conf.Unpack(&config); err != nil {
+		return nil, err
+	}
+
+	return &config.Seccomp, nil
+}
diff --git a/seccomp/seccomp_log_agent.yml b/seccomp/seccomp_log_agent.yml
new file mode 100644
index 0000000..4f261a2
--- /dev/null
+++ b/seccomp/seccomp_log_agent.yml
@@ -0,0 +1,6 @@
+seccomp:
+  default_action: log
+  syscalls:
+  - names:
+    - clock_gettime
+    action: allow
\ No newline at end of file
diff --git a/seccomp/seccomp_profile_playwright.yml b/seccomp/seccomp_profile_playwright.yml
new file mode 100644
index 0000000..df5ed34
--- /dev/null
+++ b/seccomp/seccomp_profile_playwright.yml
@@ -0,0 +1,663 @@
+seccomp:
+  defaultAction: SCMP_ACT_ERRNO
+  archMap:
+    - architecture: SCMP_ARCH_X86_64
+      subArchitectures:
+        - SCMP_ARCH_X86
+        - SCMP_ARCH_X32
+    - architecture: SCMP_ARCH_AARCH64
+      subArchitectures:
+        - SCMP_ARCH_ARM
+    - architecture: SCMP_ARCH_MIPS64
+      subArchitectures:
+        - SCMP_ARCH_MIPS
+        - SCMP_ARCH_MIPS64N32
+    - architecture: SCMP_ARCH_MIPS64N32
+      subArchitectures:
+        - SCMP_ARCH_MIPS
+        - SCMP_ARCH_MIPS64
+    - architecture: SCMP_ARCH_MIPSEL64
+      subArchitectures:
+        - SCMP_ARCH_MIPSEL
+        - SCMP_ARCH_MIPSEL64N32
+    - architecture: SCMP_ARCH_MIPSEL64N32
+      subArchitectures:
+        - SCMP_ARCH_MIPSEL
+        - SCMP_ARCH_MIPSEL64
+    - architecture: SCMP_ARCH_S390X
+      subArchitectures:
+        - SCMP_ARCH_S390
+  syscalls:
+    - comment: Allow create user namespaces
+      names:
+        - clone
+        - setns
+        - unshare
+      action: SCMP_ACT_ALLOW
+      args: []
+      includes: {}
+      excludes: {}
+    - names:
+        - accept
+        - accept4
+        - access
+        - adjtimex
+        - alarm
+        - bind
+        - brk
+        - capget
+        - capset
+        - chdir
+        - chmod
+        - chown
+        - chown32
+        - clock_adjtime
+        - clock_adjtime64
+        - clock_getres
+        - clock_getres_time64
+        - clock_gettime
+        - clock_gettime64
+        - clock_nanosleep
+        - clock_nanosleep_time64
+        - close
+        - connect
+        - copy_file_range
+        - creat
+        - dup
+        - dup2
+        - dup3
+        - epoll_create
+        - epoll_create1
+        - epoll_ctl
+        - epoll_ctl_old
+        - epoll_pwait
+        - epoll_wait
+        - epoll_wait_old
+        - eventfd
+        - eventfd2
+        - execve
+        - execveat
+        - exit
+        - exit_group
+        - faccessat
+        - fadvise64
+        - fadvise64_64
+        - fallocate
+        - fanotify_mark
+        - fchdir
+        - fchmod
+        - fchmodat
+        - fchown
+        - fchown32
+        - fchownat
+        - fcntl
+        - fcntl64
+        - fdatasync
+        - fgetxattr
+        - flistxattr
+        - flock
+        - fork
+        - fremovexattr
+        - fsetxattr
+        - fstat
+        - fstat64
+        - fstatat64
+        - fstatfs
+        - fstatfs64
+        - fsync
+        - ftruncate
+        - ftruncate64
+        - futex
+        - futex_time64
+        - futimesat
+        - getcpu
+        - getcwd
+        - getdents
+        - getdents64
+        - getegid
+        - getegid32
+        - geteuid
+        - geteuid32
+        - getgid
+        - getgid32
+        - getgroups
+        - getgroups32
+        - getitimer
+        - getpeername
+        - getpgid
+        - getpgrp
+        - getpid
+        - getppid
+        - getpriority
+        - getrandom
+        - getresgid
+        - getresgid32
+        - getresuid
+        - getresuid32
+        - getrlimit
+        - get_robust_list
+        - getrusage
+        - getsid
+        - getsockname
+        - getsockopt
+        - get_thread_area
+        - gettid
+        - gettimeofday
+        - getuid
+        - getuid32
+        - getxattr
+        - inotify_add_watch
+        - inotify_init
+        - inotify_init1
+        - inotify_rm_watch
+        - io_cancel
+        - ioctl
+        - io_destroy
+        - io_getevents
+        - io_pgetevents
+        - io_pgetevents_time64
+        - ioprio_get
+        - ioprio_set
+        - io_setup
+        - io_submit
+        - io_uring_enter
+        - io_uring_register
+        - io_uring_setup
+        - ipc
+        - kill
+        - lchown
+        - lchown32
+        - lgetxattr
+        - link
+        - linkat
+        - listen
+        - listxattr
+        - llistxattr
+        - _llseek
+        - lremovexattr
+        - lseek
+        - lsetxattr
+        - lstat
+        - lstat64
+        - madvise
+        - membarrier
+        - memfd_create
+        - mincore
+        - mkdir
+        - mkdirat
+        - mknod
+        - mknodat
+        - mlock
+        - mlock2
+        - mlockall
+        - mmap
+        - mmap2
+        - mprotect
+        - mq_getsetattr
+        - mq_notify
+        - mq_open
+        - mq_timedreceive
+        - mq_timedreceive_time64
+        - mq_timedsend
+        - mq_timedsend_time64
+        - mq_unlink
+        - mremap
+        - msgctl
+        - msgget
+        - msgrcv
+        - msgsnd
+        - msync
+        - munlock
+        - munlockall
+        - munmap
+        - nanosleep
+        - newfstatat
+        - _newselect
+        - open
+        - openat
+        - pause
+        - pipe
+        - pipe2
+        - poll
+        - ppoll
+        - ppoll_time64
+        - prctl
+        - pread64
+        - preadv
+        - preadv2
+        - prlimit64
+        - pselect6
+        - pselect6_time64
+        - pwrite64
+        - pwritev
+        - pwritev2
+        - read
+        - readahead
+        - readlink
+        - readlinkat
+        - readv
+        - recv
+        - recvfrom
+        - recvmmsg
+        - recvmmsg_time64
+        - recvmsg
+        - remap_file_pages
+        - removexattr
+        - rename
+        - renameat
+        - renameat2
+        - restart_syscall
+        - rmdir
+        - rseq
+        - rt_sigaction
+        - rt_sigpending
+        - rt_sigprocmask
+        - rt_sigqueueinfo
+        - rt_sigreturn
+        - rt_sigsuspend
+        - rt_sigtimedwait
+        - rt_sigtimedwait_time64
+        - rt_tgsigqueueinfo
+        - sched_getaffinity
+        - sched_getattr
+        - sched_getparam
+        - sched_get_priority_max
+        - sched_get_priority_min
+        - sched_getscheduler
+        - sched_rr_get_interval
+        - sched_rr_get_interval_time64
+        - sched_setaffinity
+        - sched_setattr
+        - sched_setparam
+        - sched_setscheduler
+        - sched_yield
+        - seccomp
+        - select
+        - semctl
+        - semget
+        - semop
+        - semtimedop
+        - semtimedop_time64
+        - send
+        - sendfile
+        - sendfile64
+        - sendmmsg
+        - sendmsg
+        - sendto
+        - setfsgid
+        - setfsgid32
+        - setfsuid
+        - setfsuid32
+        - setgid
+        - setgid32
+        - setgroups
+        - setgroups32
+        - setitimer
+        - setpgid
+        - setpriority
+        - setregid
+        - setregid32
+        - setresgid
+        - setresgid32
+        - setresuid
+        - setresuid32
+        - setreuid
+        - setreuid32
+        - setrlimit
+        - set_robust_list
+        - setsid
+        - setsockopt
+        - set_thread_area
+        - set_tid_address
+        - setuid
+        - setuid32
+        - setxattr
+        - shmat
+        - shmctl
+        - shmdt
+        - shmget
+        - shutdown
+        - sigaltstack
+        - signalfd
+        - signalfd4
+        - sigprocmask
+        - sigreturn
+        - socket
+        - socketcall
+        - socketpair
+        - splice
+        - stat
+        - stat64
+        - statfs
+        - statfs64
+        - statx
+        - symlink
+        - symlinkat
+        - sync
+        - sync_file_range
+        - syncfs
+        - sysinfo
+        - tee
+        - tgkill
+        - time
+        - timer_create
+        - timer_delete
+        - timer_getoverrun
+        - timer_gettime
+        - timer_gettime64
+        - timer_settime
+        - timer_settime64
+        - timerfd_create
+        - timerfd_gettime
+        - timerfd_gettime64
+        - timerfd_settime
+        - timerfd_settime64
+        - times
+        - tkill
+        - truncate
+        - truncate64
+        - ugetrlimit
+        - umask
+        - uname
+        - unlink
+        - unlinkat
+        - utime
+        - utimensat
+        - utimensat_time64
+        - utimes
+        - vfork
+        - vmsplice
+        - wait4
+        - waitid
+        - waitpid
+        - write
+        - writev
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes: {}
+      excludes: {}
+    - names:
+        - ptrace
+      action: SCMP_ACT_ALLOW
+      args: null
+      comment: ''
+      includes:
+        minKernel: '4.8'
+      excludes: {}
+    - names:
+        - personality
+      action: SCMP_ACT_ALLOW
+      args:
+        - index: 0
+          value: 0
+          valueTwo: 0
+          op: SCMP_CMP_EQ
+      comment: ''
+      includes: {}
+      excludes: {}
+    - names:
+        - personality
+      action: SCMP_ACT_ALLOW
+      args:
+        - index: 0
+          value: 8
+          valueTwo: 0
+          op: SCMP_CMP_EQ
+      comment: ''
+      includes: {}
+      excludes: {}
+    - names:
+        - personality
+      action: SCMP_ACT_ALLOW
+      args:
+        - index: 0
+          value: 131072
+          valueTwo: 0
+          op: SCMP_CMP_EQ
+      comment: ''
+      includes: {}
+      excludes: {}
+    - names:
+        - personality
+      action: SCMP_ACT_ALLOW
+      args:
+        - index: 0
+          value: 131080
+          valueTwo: 0
+          op: SCMP_CMP_EQ
+      comment: ''
+      includes: {}
+      excludes: {}
+    - names:
+        - personality
+      action: SCMP_ACT_ALLOW
+      args:
+        - index: 0
+          value: 4294967295
+          valueTwo: 0
+          op: SCMP_CMP_EQ
+      comment: ''
+      includes: {}
+      excludes: {}
+    - names:
+        - sync_file_range2
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes:
+        arches:
+          - ppc64le
+      excludes: {}
+    - names:
+        - arm_fadvise64_64
+        - arm_sync_file_range
+        - sync_file_range2
+        - breakpoint
+        - cacheflush
+        - set_tls
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes:
+        arches:
+          - arm
+          - arm64
+      excludes: {}
+    - names:
+        - arch_prctl
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes:
+        arches:
+          - amd64
+          - x32
+      excludes: {}
+    - names:
+        - modify_ldt
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes:
+        arches:
+          - amd64
+          - x32
+          - x86
+      excludes: {}
+    - names:
+        - s390_pci_mmio_read
+        - s390_pci_mmio_write
+        - s390_runtime_instr
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes:
+        arches:
+          - s390
+          - s390x
+      excludes: {}
+    - names:
+        - open_by_handle_at
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes:
+        caps:
+          - CAP_DAC_READ_SEARCH
+      excludes: {}
+    - names:
+        - bpf
+        - clone
+        - fanotify_init
+        - lookup_dcookie
+        - mount
+        - name_to_handle_at
+        - perf_event_open
+        - quotactl
+        - setdomainname
+        - sethostname
+        - setns
+        - syslog
+        - umount
+        - umount2
+        - unshare
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes:
+        caps:
+          - CAP_SYS_ADMIN
+      excludes: {}
+    - names:
+        - clone
+      action: SCMP_ACT_ALLOW
+      args:
+        - index: 0
+          value: 2114060288
+          valueTwo: 0
+          op: SCMP_CMP_MASKED_EQ
+      comment: ''
+      includes: {}
+      excludes:
+        caps:
+          - CAP_SYS_ADMIN
+        arches:
+          - s390
+          - s390x
+    - names:
+        - clone
+      action: SCMP_ACT_ALLOW
+      args:
+        - index: 1
+          value: 2114060288
+          valueTwo: 0
+          op: SCMP_CMP_MASKED_EQ
+      comment: s390 parameter ordering for clone is different
+      includes:
+        arches:
+          - s390
+          - s390x
+      excludes:
+        caps:
+          - CAP_SYS_ADMIN
+    - names:
+        - reboot
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes:
+        caps:
+          - CAP_SYS_BOOT
+      excludes: {}
+    - names:
+        - chroot
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes:
+        caps:
+          - CAP_SYS_CHROOT
+      excludes: {}
+    - names:
+        - delete_module
+        - init_module
+        - finit_module
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes:
+        caps:
+          - CAP_SYS_MODULE
+      excludes: {}
+    - names:
+        - acct
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes:
+        caps:
+          - CAP_SYS_PACCT
+      excludes: {}
+    - names:
+        - kcmp
+        - process_vm_readv
+        - process_vm_writev
+        - ptrace
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes:
+        caps:
+          - CAP_SYS_PTRACE
+      excludes: {}
+    - names:
+        - iopl
+        - ioperm
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes:
+        caps:
+          - CAP_SYS_RAWIO
+      excludes: {}
+    - names:
+        - settimeofday
+        - stime
+        - clock_settime
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes:
+        caps:
+          - CAP_SYS_TIME
+      excludes: {}
+    - names:
+        - vhangup
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes:
+        caps:
+          - CAP_SYS_TTY_CONFIG
+      excludes: {}
+    - names:
+        - get_mempolicy
+        - mbind
+        - set_mempolicy
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes:
+        caps:
+          - CAP_SYS_NICE
+      excludes: {}
+    - names:
+        - syslog
+      action: SCMP_ACT_ALLOW
+      args: []
+      comment: ''
+      includes:
+        caps:
+          - CAP_SYSLOG
+      excludes: {}
diff --git a/seccomp/x86_64_table.json b/seccomp/x86_64_table.json
new file mode 100644
index 0000000..1ad06c4
--- /dev/null
+++ b/seccomp/x86_64_table.json
@@ -0,0 +1,349 @@
+{
+    "0": "read",
+    "1": "write",
+    "2": "open",
+    "3": "close",
+    "4": "stat",
+    "5": "fstat",
+    "6": "lstat",
+    "7": "poll",
+    "8": "lseek",
+    "9": "mmap",
+    "10": "mprotect",
+    "11": "munmap",
+    "12": "brk",
+    "13": "rt_sigaction",
+    "14": "rt_sigprocmask",
+    "15": "rt_sigreturn",
+    "16": "ioctl",
+    "17": "pread",
+    "18": "pwrite",
+    "19": "readv",
+    "20": "writev",
+    "21": "access",
+    "22": "pipe",
+    "23": "select",
+    "24": "sched_yield",
+    "25": "mremap",
+    "26": "msync",
+    "27": "mincore",
+    "28": "madvise",
+    "29": "shmget",
+    "30": "shmat",
+    "31": "shmctl",
+    "32": "dup",
+    "33": "dup2",
+    "34": "pause",
+    "35": "nanosleep",
+    "36": "getitimer",
+    "37": "alarm",
+    "38": "setitimer",
+    "39": "getpid",
+    "40": "sendfile",
+    "41": "socket",
+    "42": "connect",
+    "43": "accept",
+    "44": "sendto",
+    "45": "recvfrom",
+    "46": "sendmsg",
+    "47": "recvmsg",
+    "48": "shutdown",
+    "49": "bind",
+    "50": "listen",
+    "51": "getsockname",
+    "52": "getpeername",
+    "53": "socketpair",
+    "54": "setsockopt",
+    "55": "getsockopt",
+    "56": "clone",
+    "57": "fork",
+    "58": "vfork",
+    "59": "execve",
+    "60": "exit",
+    "61": "wait4",
+    "62": "kill",
+    "63": "uname",
+    "64": "semget",
+    "65": "semop",
+    "66": "semctl",
+    "67": "shmdt",
+    "68": "msgget",
+    "69": "msgsnd",
+    "70": "msgrcv",
+    "71": "msgctl",
+    "72": "fcntl",
+    "73": "flock",
+    "74": "fsync",
+    "75": "fdatasync",
+    "76": "truncate",
+    "77": "ftruncate",
+    "78": "getdents",
+    "79": "getcwd",
+    "80": "chdir",
+    "81": "fchdir",
+    "82": "rename",
+    "83": "mkdir",
+    "84": "rmdir",
+    "85": "creat",
+    "86": "link",
+    "87": "unlink",
+    "88": "symlink",
+    "89": "readlink",
+    "90": "chmod",
+    "91": "fchmod",
+    "92": "chown",
+    "93": "fchown",
+    "94": "lchown",
+    "95": "umask",
+    "96": "gettimeofday",
+    "97": "getrlimit",
+    "98": "getrusage",
+    "99": "sysinfo",
+    "100": "times",
+    "101": "ptrace",
+    "102": "getuid",
+    "103": "syslog",
+    "104": "getgid",
+    "105": "setuid",
+    "106": "setgid",
+    "107": "geteuid",
+    "108": "getegid",
+    "109": "setpgid",
+    "110": "getppid",
+    "111": "getpgrp",
+    "112": "setsid",
+    "113": "setreuid",
+    "114": "setregid",
+    "115": "getgroups",
+    "116": "setgroups",
+    "117": "setresuid",
+    "118": "getresuid",
+    "119": "setresgid",
+    "120": "getresgid",
+    "121": "getpgid",
+    "122": "setfsuid",
+    "123": "setfsgid",
+    "124": "getsid",
+    "125": "capget",
+    "126": "capset",
+    "127": "rt_sigpending",
+    "128": "rt_sigtimedwait",
+    "129": "rt_sigqueueinfo",
+    "130": "rt_sigsuspend",
+    "131": "sigaltstack",
+    "132": "utime",
+    "133": "mknod",
+    "134": "uselib",
+    "135": "personality",
+    "136": "ustat",
+    "137": "statfs",
+    "138": "fstatfs",
+    "139": "sysfs",
+    "140": "getpriority",
+    "141": "setpriority",
+    "142": "sched_setparam",
+    "143": "sched_getparam",
+    "144": "sched_setscheduler",
+    "145": "sched_getscheduler",
+    "146": "sched_get_priority_max",
+    "147": "sched_get_priority_min",
+    "148": "sched_rr_get_interval",
+    "149": "mlock",
+    "150": "munlock",
+    "151": "mlockall",
+    "152": "munlockall",
+    "153": "vhangup",
+    "154": "modify_ldt",
+    "155": "pivot_root",
+    "156": "_sysctl",
+    "157": "prctl",
+    "158": "arch_prctl",
+    "159": "adjtimex",
+    "160": "setrlimit",
+    "161": "chroot",
+    "162": "sync",
+    "163": "acct",
+    "164": "settimeofday",
+    "165": "mount",
+    "166": "umount2",
+    "167": "swapon",
+    "168": "swapoff",
+    "169": "reboot",
+    "170": "sethostname",
+    "171": "setdomainname",
+    "172": "iopl",
+    "173": "ioperm",
+    "174": "create_module",
+    "175": "init_module",
+    "176": "delete_module",
+    "177": "get_kernel_syms",
+    "178": "query_module",
+    "179": "quotactl",
+    "180": "nfsservctl",
+    "181": "getpmsg",
+    "182": "putpmsg",
+    "183": "afs_syscall",
+    "184": "tuxcall",
+    "185": "security",
+    "186": "gettid",
+    "187": "readahead",
+    "188": "setxattr",
+    "189": "lsetxattr",
+    "190": "fsetxattr",
+    "191": "getxattr",
+    "192": "lgetxattr",
+    "193": "fgetxattr",
+    "194": "listxattr",
+    "195": "llistxattr",
+    "196": "flistxattr",
+    "197": "removexattr",
+    "198": "lremovexattr",
+    "199": "fremovexattr",
+    "200": "tkill",
+    "201": "time",
+    "202": "futex",
+    "203": "sched_setaffinity",
+    "204": "sched_getaffinity",
+    "205": "set_thread_area",
+    "206": "io_setup",
+    "207": "io_destroy",
+    "208": "io_getevents",
+    "209": "io_submit",
+    "210": "io_cancel",
+    "211": "get_thread_area",
+    "212": "lookup_dcookie",
+    "213": "epoll_create",
+    "214": "epoll_ctl_old",
+    "215": "epoll_wait_old",
+    "216": "remap_file_pages",
+    "217": "getdents64",
+    "218": "set_tid_address",
+    "219": "restart_syscall",
+    "220": "semtimedop",
+    "221": "fadvise64",
+    "222": "timer_create",
+    "223": "timer_settime",
+    "224": "timer_gettime",
+    "225": "timer_getoverrun",
+    "226": "timer_delete",
+    "227": "clock_settime",
+    "228": "clock_gettime",
+    "229": "clock_getres",
+    "230": "clock_nanosleep",
+    "231": "exit_group",
+    "232": "epoll_wait",
+    "233": "epoll_ctl",
+    "234": "tgkill",
+    "235": "utimes",
+    "236": "vserver",
+    "237": "mbind",
+    "238": "set_mempolicy",
+    "239": "get_mempolicy",
+    "240": "mq_open",
+    "241": "mq_unlink",
+    "242": "mq_timedsend",
+    "243": "mq_timedreceive",
+    "244": "mq_notify",
+    "245": "mq_getsetattr",
+    "246": "kexec_load",
+    "247": "waitid",
+    "248": "add_key",
+    "249": "request_key",
+    "250": "keyctl",
+    "251": "ioprio_set",
+    "252": "ioprio_get",
+    "253": "inotify_init",
+    "254": "inotify_add_watch",
+    "255": "inotify_rm_watch",
+    "256": "migrate_pages",
+    "257": "openat",
+    "258": "mkdirat",
+    "259": "mknodat",
+    "260": "fchownat",
+    "261": "futimesat",
+    "262": "newfstatat",
+    "263": "unlinkat",
+    "264": "renameat",
+    "265": "linkat",
+    "266": "symlinkat",
+    "267": "readlinkat",
+    "268": "fchmodat",
+    "269": "faccessat",
+    "270": "pselect6",
+    "271": "ppoll",
+    "272": "unshare",
+    "273": "set_robust_list",
+    "274": "get_robust_list",
+    "275": "splice",
+    "276": "tee",
+    "277": "sync_file_range",
+    "278": "vmsplice",
+    "279": "move_pages",
+    "280": "utimensat",
+    "281": "epoll_pwait",
+    "282": "signalfd",
+    "283": "timerfd_create",
+    "284": "eventfd",
+    "285": "fallocate",
+    "286": "timerfd_settime",
+    "287": "timerfd_gettime",
+    "288": "accept4",
+    "289": "signalfd4",
+    "290": "eventfd2",
+    "291": "epoll_create1",
+    "292": "dup3",
+    "293": "pipe2",
+    "294": "inotify_init1",
+    "295": "preadv",
+    "296": "pwritev",
+    "297": "rt_tgsigqueueinfo",
+    "298": "perf_event_open",
+    "299": "recvmmsg",
+    "300": "fanotify_init",
+    "301": "fanotify_mark",
+    "302": "prlimit64",
+    "303": "name_to_handle_at",
+    "304": "open_by_handle_at",
+    "305": "clock_adjtime",
+    "306": "syncfs",
+    "307": "sendmmsg",
+    "308": "setns",
+    "309": "getcpu",
+    "310": "process_vm_readv",
+    "311": "process_vm_writev",
+    "312": "kcmp",
+    "313": "finit_module",
+    "314": "sched_setattr",
+    "315": "sched_getattr",
+    "316": "renameat2",
+    "317": "seccomp",
+    "318": "getrandom",
+    "319": "memfd_create",
+    "320": "kexec_file_load",
+    "321": "bpf",
+    "322": "execveat",
+    "323": "userfaultfd",
+    "324": "membarrier",
+    "325": "mlock2",
+    "326": "copy_file_range",
+    "327": "preadv2",
+    "328": "pwritev2",
+    "329": "pkey_mprotect",
+    "330": "pkey_alloc",
+    "331": "pkey_free",
+    "332": "statx",
+    "333": "io_pgetevents",
+    "334": "rseq",
+    "424": "pidfd_send_signal",
+    "425": "io_uring_setup",
+    "426": "io_uring_enter",
+    "427": "io_uring_register",
+    "428": "open_tree",
+    "429": "move_mount",
+    "430": "fsopen",
+    "431": "fsconfig",
+    "432": "fsmount",
+    "433": "fspick",
+    "434": "pidfd_open",
+    "435": "clone3"
+}
\ No newline at end of file
diff --git a/src/browser-service.ts b/src/browser-service.ts
index 99470cc..91cd2f0 100644
--- a/src/browser-service.ts
+++ b/src/browser-service.ts
@@ -31,6 +31,12 @@ import { chromium } from 'playwright-chromium';
 const proxy = createProxyServer();
 const proxyServer = createServer();
 
+/**
+ * TODO: refactor browser service into a class
+ * to manage this state with init/dispose.
+ */
+let closeCallbacks = [];
+
 proxyServer.on('upgrade', async function (req, socket, head) {
   const browserServer = await chromium.launchServer({ headless: true });
   const wsEndpoint = browserServer.wsEndpoint();
@@ -39,9 +45,14 @@ proxyServer.on('upgrade', async function (req, socket, head) {
   console.log(`New browser: ${wsEndpoint}`);
   proxy.ws(req, socket, head, { target: origin });
   const closeBrowser = async () => {
+    const index = closeCallbacks.indexOf(closeBrowser);
+    if (index >= 0) {
+      closeCallbacks.splice(index, 1);
+    }
     await browserServer.close();
     console.log(`Socket closed: ${wsEndpoint}`);
   };
+  closeCallbacks.push(closeBrowser);
   socket.on('close', closeBrowser);
   socket.on('error', closeBrowser);
 });
@@ -50,3 +61,12 @@ const port = 9322;
 proxyServer.listen(port, () => {
   console.log(`Listening on port: ${port}`);
 });
+
+process.on('SIGTERM', async () => {
+  for (let i = 0; i < closeCallbacks.length; i++) {
+    const callback = closeCallbacks[i];
+    await callback();
+  }
+  closeCallbacks = [];
+  process.exit(0);
+});


Test patch:
diff --git a/__tests__/core/gatherer.test.ts b/__tests__/core/gatherer.test.ts
index b095dcc..adfc062 100644
--- a/__tests__/core/gatherer.test.ts
+++ b/__tests__/core/gatherer.test.ts
@@ -26,18 +26,19 @@
 import { Gatherer } from '../../src/core/gatherer';
 import { PluginManager } from '../../src/plugins';
 import { NetworkManager } from '../../src/plugins/network';
+import { wsEndpoint } from '../utils/test-config';
 
 jest.mock('../../src/plugins/network');
 
 describe('Gatherer', () => {
   it('boot and dispose driver', async () => {
-    const driver = await Gatherer.setupDriver(true);
+    const driver = await Gatherer.setupDriver(true, wsEndpoint);
     expect(typeof driver.page.goto).toBe('function');
     await Gatherer.dispose(driver);
   });
 
   it('begin recording based on flags', async () => {
-    const driver = await Gatherer.setupDriver(true);
+    const driver = await Gatherer.setupDriver(true, wsEndpoint);
     const pluginManager = await Gatherer.beginRecording(driver, {
       network: true,
     });
diff --git a/__tests__/core/runner.test.ts b/__tests__/core/runner.test.ts
index 6722490..9d6cbf8 100644
--- a/__tests__/core/runner.test.ts
+++ b/__tests__/core/runner.test.ts
@@ -28,8 +28,9 @@ import { Gatherer } from '../../src/core/gatherer';
 import Runner from '../../src/core/runner';
 import { step, journey } from '../../src/core';
 import { Journey } from '../../src/dsl';
-import { Server } from '../../utils/server';
+import { Server } from '../utils/server';
 import { generateTempPath } from '../../src/helpers';
+import { wsEndpoint } from '../utils/test-config';
 
 describe('runner', () => {
   let runner: Runner, server: Server;
@@ -104,7 +105,7 @@ describe('runner', () => {
         end: expect.any(Number),
       });
     });
-    const result = await runner.runJourney(journey, {});
+    const result = await runner.runJourney(journey, { wsEndpoint });
     expect(result.status).toBe('succeeded');
   });
 
@@ -115,7 +116,7 @@ describe('runner', () => {
     journey.addStep('step2', async () => {
       throw error;
     });
-    const result = await runner.runJourney(journey, {});
+    const result = await runner.runJourney(journey, { wsEndpoint });
     expect(result).toEqual({
       status: 'failed',
       error,
@@ -126,7 +127,7 @@ describe('runner', () => {
     const journey = new Journey({ name: 'with hooks' }, noop);
     journey.addHook('before', noop);
     journey.addHook('after', noop);
-    const result = await runner.runJourney(journey, {});
+    const result = await runner.runJourney(journey, { wsEndpoint });
     expect(result).toEqual({
       status: 'succeeded',
     });
@@ -139,7 +140,7 @@ describe('runner', () => {
     journey.addHook('after', () => {
       throw error;
     });
-    const result = await runner.runJourney(journey, {});
+    const result = await runner.runJourney(journey, { wsEndpoint });
     expect(result).toEqual({
       status: 'failed',
       error,
@@ -221,6 +222,7 @@ describe('runner', () => {
     const journey = new Journey({ name }, noop);
     runner.addJourney(journey);
     const result = await runner.run({
+      wsEndpoint,
       outfd: fs.openSync(dest, 'w'),
     });
     expect(result).toEqual({
@@ -232,6 +234,7 @@ describe('runner', () => {
     runner.addJourney(new Journey({ name: 'j1' }, noop));
     runner.addJourney(new Journey({ name: 'j2' }, noop));
     const result = await runner.run({
+      wsEndpoint,
       outfd: fs.openSync(dest, 'w'),
       journeyName: 'j2',
     });
@@ -249,6 +252,7 @@ describe('runner', () => {
     });
     runner.addJourney(j2);
     const result = await runner.run({
+      wsEndpoint,
       outfd: fs.openSync(dest, 'w'),
     });
     expect(result).toEqual({
@@ -263,6 +267,7 @@ describe('runner', () => {
     let count = 0;
     runner.on('journey:register', () => count++);
     const result = await runner.run({
+      wsEndpoint,
       outfd: fs.openSync(dest, 'w'),
       dryRun: true,
     });
@@ -288,6 +293,7 @@ describe('runner', () => {
     runner.addJourney(j2);
 
     await runner.run({
+      wsEndpoint,
       outfd: fs.openSync(dest, 'w'),
     });
     expect(result).toEqual([
diff --git a/__tests__/plugins/browser-console.test.ts b/__tests__/plugins/browser-console.test.ts
index bd26be0..0abb670 100644
--- a/__tests__/plugins/browser-console.test.ts
+++ b/__tests__/plugins/browser-console.test.ts
@@ -25,7 +25,8 @@
 
 import { Gatherer } from '../../src/core/gatherer';
 import { BrowserConsole } from '../../src/plugins';
-import { Server } from '../../utils/server';
+import { Server } from '../utils/server';
+import { wsEndpoint } from '../utils/test-config';
 
 describe('BrowserConsole', () => {
   let server: Server;
@@ -37,7 +38,7 @@ describe('BrowserConsole', () => {
   });
 
   it('should capture browser console logs', async () => {
-    const driver = await Gatherer.setupDriver();
+    const driver = await Gatherer.setupDriver(true, wsEndpoint);
     const browserConsole = new BrowserConsole(driver.page);
     browserConsole.start();
     await driver.page.goto(server.TEST_PAGE);
diff --git a/__tests__/plugins/network.test.ts b/__tests__/plugins/network.test.ts
index 9f8a71e..4991817 100644
--- a/__tests__/plugins/network.test.ts
+++ b/__tests__/plugins/network.test.ts
@@ -25,7 +25,8 @@
 
 import { Gatherer } from '../../src/core/gatherer';
 import { NetworkManager } from '../../src/plugins/network';
-import { Server } from '../../utils/server';
+import { Server } from '../utils/server';
+import { wsEndpoint } from '../utils/test-config';
 
 describe('network', () => {
   let server: Server;
@@ -36,8 +37,8 @@ describe('network', () => {
     await server.close();
   });
 
-  it('capture network info', async () => {
-    const driver = await Gatherer.setupDriver();
+  it('should capture network info', async () => {
+    const driver = await Gatherer.setupDriver(true, wsEndpoint);
     const network = new NetworkManager();
     await network.start(driver.client);
     await driver.page.goto(server.TEST_PAGE);
@@ -53,7 +54,7 @@ describe('network', () => {
   });
 
   it('produce distinct events for redirects', async () => {
-    const driver = await Gatherer.setupDriver();
+    const driver = await Gatherer.setupDriver(true, wsEndpoint);
     const network = new NetworkManager();
     await network.start(driver.client);
     /**
diff --git a/__tests__/plugins/performance.test.ts b/__tests__/plugins/performance.test.ts
index c5d34ee..179854b 100644
--- a/__tests__/plugins/performance.test.ts
+++ b/__tests__/plugins/performance.test.ts
@@ -25,7 +25,8 @@
 
 import { Gatherer } from '../../src/core/gatherer';
 import { PerformanceManager } from '../../src/plugins/performance';
-import { Server } from '../../utils/server';
+import { Server } from '../utils/server';
+import { wsEndpoint } from '../utils/test-config';
 
 describe('performance', () => {
   let server: Server;
@@ -37,7 +38,7 @@ describe('performance', () => {
   });
 
   it('should capture page metrics', async () => {
-    const driver = await Gatherer.setupDriver();
+    const driver = await Gatherer.setupDriver(true, wsEndpoint);
     const performance = new PerformanceManager(driver.client);
     await performance.start();
     await driver.page.goto(server.TEST_PAGE);
diff --git a/__tests__/plugins/tracing.test.ts b/__tests__/plugins/tracing.test.ts
index dc70691..de50e7a 100644
--- a/__tests__/plugins/tracing.test.ts
+++ b/__tests__/plugins/tracing.test.ts
@@ -25,7 +25,8 @@
 
 import { Tracing, filterFilmstrips } from '../../src/plugins/tracing';
 import { Gatherer } from '../../src/core/gatherer';
-import { Server } from '../../utils/server';
+import { Server } from '../utils/server';
+import { wsEndpoint } from '../utils/test-config';
 
 describe('tracing', () => {
   let server: Server;
@@ -37,7 +38,7 @@ describe('tracing', () => {
   });
 
   it('should capture filmstrips', async () => {
-    const driver = await Gatherer.setupDriver();
+    const driver = await Gatherer.setupDriver(true, wsEndpoint);
     const tracer = new Tracing();
     await tracer.start(driver.client);
     await driver.page.goto(server.TEST_PAGE);
diff --git a/__tests__/utils/jest-global-setup.ts b/__tests__/utils/jest-global-setup.ts
new file mode 100644
index 0000000..6cae183
--- /dev/null
+++ b/__tests__/utils/jest-global-setup.ts
@@ -0,0 +1,51 @@
+/**
+ * MIT License
+ *
+ * Copyright (c) 2020-present, Elastic NV
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+import { spawn } from 'child_process';
+import { wsEndpoint } from './test-config';
+
+module.exports = async () => {
+  if (wsEndpoint) {
+    return new Promise((resolve, reject) => {
+      console.log(`\nRunning BrowserService ${wsEndpoint}`);
+      const process = spawn('node', ['./dist/browser-service.js']);
+      (global as any).__browserServiceProcess = process;
+      process.stdout.on('data', data => {
+        if (data.indexOf('Listening on port: 9322') >= 0) {
+          console.log(`${data}`);
+          resolve(process);
+        }
+      });
+
+      process.stderr.on('data', data => {
+        const message = `BrowserService: ${data}`;
+        process.kill();
+        reject(message);
+      });
+    });
+  } else {
+    console.log(`\nRunning without BrowserService`);
+  }
+};
diff --git a/__tests__/utils/jest-global-teardown.ts b/__tests__/utils/jest-global-teardown.ts
new file mode 100644
index 0000000..0e8ece4
--- /dev/null
+++ b/__tests__/utils/jest-global-teardown.ts
@@ -0,0 +1,31 @@
+/**
+ * MIT License
+ *
+ * Copyright (c) 2020-present, Elastic NV
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+module.exports = async () => {
+  const browserService = (global as any).__browserServiceProcess;
+  if (browserService) {
+    browserService.kill();
+  }
+};
diff --git a/utils/server.ts b/__tests__/utils/server.ts
similarity index 100%
rename from utils/server.ts
rename to __tests__/utils/server.ts
diff --git a/__tests__/utils/test-config.ts b/__tests__/utils/test-config.ts
new file mode 100644
index 0000000..ee2397d
--- /dev/null
+++ b/__tests__/utils/test-config.ts
@@ -0,0 +1,26 @@
+/**
+ * MIT License
+ *
+ * Copyright (c) 2020-present, Elastic NV
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+export const wsEndpoint = process.env.WSENDPOINT;