Skip to content

feat(ssl): add secure key wiping and tmpfs for SSL Bump CA keys#262

Closed
Copilot wants to merge 5 commits intomainfrom
copilot/secure-ssl-bump-key-wiping
Closed

feat(ssl): add secure key wiping and tmpfs for SSL Bump CA keys#262
Copilot wants to merge 5 commits intomainfrom
copilot/secure-ssl-bump-key-wiping

Conversation

Copy link
Contributor

Copilot AI commented Jan 17, 2026

SSL Bump CA private keys were stored on disk with file permissions as sole protection, creating exposure windows during container escape scenarios.

Changes

Secure Key Wiping (src/ssl-bump.ts)

  • secureWipeFile(): Overwrites with crypto random data (3 passes, 64KB chunks), syncs to disk, then deletes
  • cleanupSslBumpFiles(): Wipes private key, removes certificates and SSL database

tmpfs for SSL Certificate Database

  • Replaced volume mount with tmpfs at /var/spool/squid_ssl_db (64MB limit, mode 0700)
  • Dynamically generated certificates never touch disk

Container Updates

  • Updated Squid entrypoint to initialize SSL DB structure on tmpfs at container start
  • Cleanup now calls cleanupSslBumpFiles() before deleting workDir

Types

  • Added tmpfs property to DockerService interface

Example

// Private key is overwritten 3x with random data before deletion
await secureWipeFile('/tmp/awf-123/ssl/ca-key.pem');

// Full cleanup during session end
await cleanupSslBumpFiles(workDir);

Tests

Added 7 tests covering secure wipe and cleanup edge cases.

Original prompt

This section details on the original issue you should resolve

<issue_title>[Security] H2: SSL Bump Key Exposure Risk - Implement secure key wiping + tmpfs</issue_title>
<issue_description>## Priority
High

Description

The SSL Bump feature generates a per-session CA private key stored on disk with file permissions as sole protection. This creates exposure windows during container escape scenarios where an attacker could access the private key.

Impact

  • Severity: High
  • Attack Vector: Container escape could expose CA private key
  • Risk: SSL interception, man-in-the-middle attacks

Proposed Solution

  1. Store SSL Bump CA keys in tmpfs (memory-only filesystem)
  2. Implement secure key wiping when cleaning up
  3. Minimize key lifetime and exposure window

Effort Estimate

~4 hours

References

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits January 17, 2026 04:34
Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com>
Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com>
Copilot AI changed the title [WIP] Implement secure key wiping for SSL Bump feat(ssl): add secure key wiping and tmpfs for SSL Bump CA keys Jan 17, 2026
Copilot AI requested a review from Mossaka January 17, 2026 04:40
@Mossaka Mossaka marked this pull request as ready for review January 17, 2026 09:11
@github-actions
Copy link

github-actions bot commented Jan 17, 2026

⚠️ Coverage Regression Detected

This PR decreases test coverage. Please add tests to maintain coverage levels.

Overall Coverage

Metric Base PR Delta
Lines 77.19% 77.51% 📈 +0.32%
Statements 77.27% 77.59% 📈 +0.32%
Functions 77.17% 77.41% 📈 +0.24%
Branches 69.76% 69.66% 📉 -0.10%
📁 Per-file Coverage Changes (2 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 75.9% → 76.0% (+0.15%) 75.2% → 75.4% (+0.15%)
src/ssl-bump.ts 32.1% → 60.2% (+28.02%) 32.1% → 60.5% (+28.36%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

}

// Open file for writing (not appending)
const fd = fs.openSync(filePath, 'w');

Check failure

Code scanning / CodeQL

Potential file system race condition High

The file may have changed since it
was checked
.
The file may have changed since it
was checked
.
}

// Open file for writing (not appending)
const fd = fs.openSync(filePath, 'w');

Check failure

Code scanning / CodeQL

Insecure temporary file High

Insecure creation of file in
the os temp dir
.

Copilot Autofix

AI 19 days ago

In general, to fix insecure temporary file issues in Node.js, avoid constructing paths in the OS temp directory from untrusted data and then creating files with fs.open/fs.openSync using generic modes like 'w'. Instead, either (a) use a library like tmp that creates securely‑permissioned, unique files, or (b) ensure the file is already expected to exist and open it with flags that will not silently create it (e.g., fs.constants.O_WRONLY | fs.constants.O_EXCL plus O_TRUNC on a verified existing file).

For this specific code, the best change with minimal impact is to harden secureWipeFile so that it can never create a new file: it should fail if the target file does not already exist at fs.openSync time. We already do an existence check, but that’s subject to a race. We can instead replace the call fs.openSync(filePath, 'w') with a call that uses explicit flags O_WRONLY | O_TRUNC, which by themselves still create a file if it does not exist, but we can add O_EXCL or use O_NOCTTY depending on platform. Node doesn’t support a “open only if exists” flag cross‑platform, but we can remove the initial existsSync and instead wrap openSync in a try/catch, treating ENOENT as a benign “file already gone” situation. This changes the flow to: try to open; if it fails with ENOENT, log and return (no file to wipe); otherwise proceed. Because we never create the file elsewhere using secureWipeFile, using fs.openSync(filePath, 'r+') (read/write) is the right semantic: it will fail with ENOENT instead of creating a new file. That fully removes the insecure “create file” behavior while preserving functionality: we still overwrite and delete existing sensitive files, and we safely ignore files that disappear between checks.

Concretely:

  • In src/ssl-bump.ts, in secureWipeFile, remove the initial fs.existsSync check.
  • Change fs.openSync(filePath, 'w') to fs.openSync(filePath, 'r+') and handle ENOENT from openSync as a non‑error (just log debug and return), while other errors still go through the existing catch block.
  • Keep the rest of the overwrite and delete logic unchanged.

No changes are required in docker-manager.ts or its tests for this specific sink; we only need to harden the sink (secureWipeFile).

Suggested changeset 1
src/ssl-bump.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/ssl-bump.ts b/src/ssl-bump.ts
--- a/src/ssl-bump.ts
+++ b/src/ssl-bump.ts
@@ -184,11 +184,6 @@
  * @throws Error if file operations fail (after attempting cleanup)
  */
 export async function secureWipeFile(filePath: string): Promise<void> {
-  if (!fs.existsSync(filePath)) {
-    logger.debug(`File not found for secure wipe: ${filePath}`);
-    return;
-  }
-
   try {
     const stats = fs.statSync(filePath);
     const fileSize = stats.size;
@@ -200,8 +195,18 @@
       return;
     }
 
-    // Open file for writing (not appending)
-    const fd = fs.openSync(filePath, 'w');
+    // Open existing file for read/write (do not create if missing)
+    let fd: number;
+    try {
+      fd = fs.openSync(filePath, 'r+');
+    } catch (openError: any) {
+      // If the file disappeared between stat and open, treat as already gone
+      if (openError && (openError as NodeJS.ErrnoException).code === 'ENOENT') {
+        logger.debug(`File not found for secure wipe (disappeared before open): ${filePath}`);
+        return;
+      }
+      throw openError;
+    }
 
     try {
       // Use chunked writing for memory efficiency
EOF
@@ -184,11 +184,6 @@
* @throws Error if file operations fail (after attempting cleanup)
*/
export async function secureWipeFile(filePath: string): Promise<void> {
if (!fs.existsSync(filePath)) {
logger.debug(`File not found for secure wipe: ${filePath}`);
return;
}

try {
const stats = fs.statSync(filePath);
const fileSize = stats.size;
@@ -200,8 +195,18 @@
return;
}

// Open file for writing (not appending)
const fd = fs.openSync(filePath, 'w');
// Open existing file for read/write (do not create if missing)
let fd: number;
try {
fd = fs.openSync(filePath, 'r+');
} catch (openError: any) {
// If the file disappeared between stat and open, treat as already gone
if (openError && (openError as NodeJS.ErrnoException).code === 'ENOENT') {
logger.debug(`File not found for secure wipe (disappeared before open): ${filePath}`);
return;
}
throw openError;
}

try {
// Use chunked writing for memory efficiency
Copilot is powered by AI and may make mistakes. Always verify output.
@github-actions
Copy link

github-actions bot commented Jan 17, 2026

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

@github-actions
Copy link

github-actions bot commented Jan 17, 2026

🌑 The shadows whisper... Smoke Codex failed. The oracle requires further meditation...

@github-actions
Copy link

github-actions bot commented Jan 17, 2026

🎬 THE ENDSmoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨

@github-actions
Copy link

Smoke Test Results

Last 2 Merged PRs:

Test Results:

  • ✅ GitHub MCP Testing
  • ❌ Playwright Testing (browser installation/network issues)
  • ✅ File Writing Testing
  • ✅ Bash Tool Testing

Overall Status: ❌ FAIL

cc: @Mossaka (author/assignee)

AI generated by Smoke Copilot

@github-actions
Copy link

Smoke Test Results - Claude Engine

Last 2 Merged PRs:

Test Results:

  • ✅ GitHub MCP: Retrieved PRs successfully
  • ✅ Playwright: Page title verified ("GitHub · Change is constant. GitHub keeps you ahead. · GitHub")
  • ✅ File Writing: Created /tmp/gh-aw/agent/smoke-test-claude-21092104015.txt
  • ✅ Bash Tool: Verified file contents

Overall Status: PASS

AI generated by Smoke Claude

@github-actions
Copy link

github-actions bot commented Jan 17, 2026

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

@github-actions
Copy link

github-actions bot commented Jan 17, 2026

🎬 THE ENDSmoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨

@github-actions
Copy link

github-actions bot commented Jan 17, 2026

🌑 The shadows whisper... Smoke Codex failed. The oracle requires further meditation...

@github-actions
Copy link

⚠️ Coverage Regression Detected

This PR decreases test coverage. Please add tests to maintain coverage levels.

Overall Coverage

Metric Base PR Delta
Lines 77.19% 77.51% 📈 +0.32%
Statements 77.27% 77.59% 📈 +0.32%
Functions 77.17% 77.41% 📈 +0.24%
Branches 69.76% 69.66% 📉 -0.10%
📁 Per-file Coverage Changes (2 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 75.9% → 76.0% (+0.15%) 75.2% → 75.4% (+0.15%)
src/ssl-bump.ts 32.1% → 60.2% (+28.02%) 32.1% → 60.5% (+28.36%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

@github-actions
Copy link

Smoke Test Results

Last 2 merged PRs:

Test Results:

  • ✅ GitHub MCP: Successfully fetched PRs
  • ✅ Playwright: Page title contains "GitHub"
  • ✅ File Writing: Created test file successfully
  • ✅ Bash Tool: Verified file contents

Status: PASS

AI generated by Smoke Claude

@github-actions
Copy link

Smoke Test Results

Last 2 Merged PRs:

Test Results:

  • ✅ GitHub MCP: Retrieved PR data successfully
  • ❌ Playwright: Missing system library (libglib-2.0.so.0)
  • ✅ File Writing: Created /tmp/gh-aw/agent/smoke-test-copilot-21092324576.txt
  • ✅ Bash Tool: File content verified

Overall: FAIL (Playwright test failed)

cc: @Mossaka @copilot

AI generated by Smoke Copilot

@Mossaka Mossaka closed this Jan 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] H2: SSL Bump Key Exposure Risk - Implement secure key wiping + tmpfs

2 participants