diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..65603e7
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: FSFAP
+# SPDX-FileCopyrightText: Copyright (c) 2024 Rifa Achrinza
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+indent_style = space
+indent_size = 2
+max_line_length = 80
diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml
new file mode 100644
index 0000000..bc9de68
--- /dev/null
+++ b/.github/workflows/cd.yaml
@@ -0,0 +1,70 @@
+name: CD
+
+on:
+  push: {} # Remove me!
+  workflow_dispatch:
+    inputs:
+      branch:
+        description: Branch to publish a release from
+        required: true
+        default: main
+        type: choice
+        options:
+          - main
+          - v9.x
+  
+permissions: {}
+
+jobs:
+  test:
+    name: Test
+    uses: ./.github/workflows/ci.yaml
+  build:
+    name: Build
+    permissions:
+      id-token: write
+      contents: read
+      actions: read
+    # Do not pin to hash
+    # See: https://github.com/slsa-framework/slsa-verifier/issues/12
+    uses: slsa-framework/slsa-github-generator/.github/workflows/builder_nodejs_slsa3.yml@v2.0.0
+    with:
+      node-version: 22
+      run-scripts: ci, test
+  publish:
+    name: Publish
+    runs-on: ubuntu-24.04
+    needs: [build]
+    permissions:
+      content: read
+      id-token: write
+    steps:
+      - name: Download Tarball
+        uses: slsa-framework/slsa-github-generator/actions/nodejs/secure-package-download@v2.0.0
+        with:
+          name: ${{ needs.build.outputs.package-download-name }}
+          path: ${{ needs.build.outputs.package-name }}
+          sha256: ${{ needs.build.outputs.package-download-sha256 }}
+      - name: Download Provenance
+        uses: slsa-framework/slsa-github-generator/actions/nodejs/secure-attestations-download@v2.0.0
+        with:
+          name: ${{ needs.build.outputs.provenance-download-name }}
+          path: attestations
+          sha256: ${{ needs.build.outputs.provenance-download-sha256 }}
+      - name: Request for NPM 2FA Code
+        uses: step-security/wait-for-secrets@5809f7d044804a5a1d43217fa8f3e855939fc9ef # v1.2.0
+        with:
+          secrets: |
+            npm-otp:
+              name: NPM Registry OTP
+              description: NPM Registry TOTP code for `achrinza-bot` NPM account
+      - name: Publish Package
+        env:
+          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+          TARBALL_PATH: ${{ needs.build.outputs.package-name }}
+          PROVENANCE_PATH: ./attestations/${{ needs.build.outputs.provenance-name }}
+        run: |
+          npm publish \
+            --access=public \
+            --provenance-file="$PROVENANCE_PATH" \
+            "$TARBALL_PATH"
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 08665ca..09928e7 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -2,10 +2,13 @@ name: CI
 
 on:
   push:
-    branches: [ main ]
+    branches: [main]
   pull_request:
-    branches: [ main ]
+    branches: [main]
+  workflow_call: {}
 
+permissions: {}
+    
 jobs:
   test:
     name: Test
@@ -13,9 +16,9 @@ jobs:
     strategy:
       matrix:
         os:
-          - ubuntu-latest
+          - ubuntu-24.04
           - macos-13
-          - windows-latest
+          - windows-2022
         node-version:
           - 14
           - 16
@@ -25,31 +28,36 @@ jobs:
           - 20
           - 21
           - 22
+          - 23
     steps:
-      - uses: actions/checkout@v2
+      - name: Checkout Repository
+        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
       - name: Use Node.js ${{ matrix.node-version }}
-        uses: actions/setup-node@v1
+        uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
         with:
           node-version: ${{ matrix.node-version }}
+          cache: npm
       - name: Install Dependencies
-        run: npm ci --ignore-scripts
+        run: npm ci --prefer-offline --ignore-scripts
       - name: Run Tests
-        run: npm test
+        run: npm test --ignore-scripts
   code-lint:
     name: Code Lint
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
-      - name: Use Node.js 16
-        uses: actions/setup-node@v1
+      - name: Checkout Repository
+        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+      - name: Use Node.js 22
+        uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
         with:
-          node-version: 16 # LTS
+          node-version: 22 # LTS
+          cache: npm
       - name: Install Dependencies
-        run: npm ci --ignore-scripts
+        run: npm ci --prefer-offline --ignore-scripts
       - name: Lockfile Lint
         run: |
           npm exec \
-            --no-install \
+            --no \
             --package=lockfile-lint \
             -- \
             lockfile-lint \
diff --git a/.gitignore b/.gitignore
index fc48ac5..592fbe2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,8 @@
 npm-debug.log
 node-ipc.cjs
 /coverage/
+
+# Code editors
+\#*#
+.#*
+*~
diff --git a/RELEASE.md b/RELEASE.md
new file mode 100644
index 0000000..9eba123
--- /dev/null
+++ b/RELEASE.md
@@ -0,0 +1,44 @@
+# Publishing a Release
+
+This project uses a manually-invoked GitHub Actions workflow to publish its packages.
+
+This workflow uses:
+- The SLSA Node.js builder to achieve SLSA v3-level provenance
+- An NPM granular access token for `achrinza-bot`
+
+## Publishing
+
+Before continuing, enusre that you have:
+
+1. A GitHub account with the `Repository Admin` role for the `achrinza/node-ipc` repository
+2. The username and password for the `achrinza-bot` NPM account or any account that has:
+  1. TOTP 2FA enabled
+  2. Write access to the `@achrinza/node-ipc` NPM package
+3. The TOTP generator for said account
+
+### 1. Regenerating the NPM Granular Token
+
+1. Go to [New Granular Access Token](https://www.npmjs.com/settings/achrinza/tokens/granular-access-tokens/new)
+2. Generate a granular access token that:
+  1. expires in 1 day
+  2. has `Read and write` permssions for only the `@achrinza/node-ipc` package.
+3. Click `Generate` and copy the token
+
+### 2. Creating a GitHub "Release"
+
+1. Delete and re-fetch all Git tags
+   This is necessary to prevent accidental tags from being pushed to the GitHub repository
+   
+   On Linux or macOS:
+   ```sh
+   $ git branch -l | xargs -I{} git branch -d {}
+   $ git fetch 'refs/tags/*:refs/tags/*'
+   ```
+   
+   On Windows ()
+
+### 3. Creating an NPM Release
+
+1. Go to [Update Action secret NPM_TOKEN](https://github.com/achrinza/node-ipc/settings/secrets/actions/NPM_TOKEN)
+2. Paste the token and click `Update secret`
+3. Go to [CD * workflow runs](https://github.com/achrinza/node-ipc/actions/workflows/cd.yaml)
diff --git a/package.json b/package.json
index c329b3a..b5fd11d 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
     "node-http-server": "^8.1.4"
   },
   "scripts": {
+    "ci": "npm ci",
     "prepare": "esbuild node-ipc.js --bundle --format=cjs --target=es2018 --platform=node --outfile=node-ipc.cjs",
     "test": "c8 -r lcov -r html node test/CI.js && c8 report && node ./lcov.js",
     "coverage": "echo 'See your coverage report at http://localhost:8080' && node-http-server port=8080 root=./coverage/"