Skip to content

fix: harden synapse config perms and remove plaintext bridge tokens#60

Open
altantutar wants to merge 1 commit into
serrrfirat:mainfrom
altantutar:codex/issue-46-secrets-perms
Open

fix: harden synapse config perms and remove plaintext bridge tokens#60
altantutar wants to merge 1 commit into
serrrfirat:mainfrom
altantutar:codex/issue-46-secrets-perms

Conversation

@altantutar

@altantutar altantutar commented Feb 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • move bridge secrets out of persisted ~/.synapse/bridge.json by default (env-only for GATEWAY_AUTH_TOKEN / IRONCLAW_TOKEN)
  • migrate legacy plaintext token fields out of existing bridge.json on read
  • enforce restrictive local permissions for ~/.synapse config artifacts (dir 0700, files 0600)
  • update setup/onboarding messaging for env-only secret handling
  • add/adjust tests for secret persistence behavior and permission hardening

Validation

  • bunx tsc --noEmit
  • bun test

Closes #46

Summary by CodeRabbit

  • Security Enhancements

    • Configuration and bridge files now use stricter file permissions (user-only read/write access).
    • Configuration directories are created with secure default permissions.
  • Feature Changes

    • Authentication tokens are now stored as environment variables instead of in configuration files for improved security.
  • Improvements

    • Updated setup wizard to guide users to provide tokens via environment variables.
    • Legacy token fields in stored configurations are automatically migrated.
  • Tests

    • Added comprehensive test coverage for configuration management and permission handling.

@coderabbitai

coderabbitai Bot commented Feb 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR implements filesystem hardening and moves bridge tokens from plaintext config to environment-only storage. A new fs-security module provides utilities to enforce restrictive permissions (0o700 for directories, 0o600 for files) across config storage. Configuration modules are refactored to strip sensitive fields from persisted JSON and restore them from environment variables or in-memory storage.

Changes

Cohort / File(s) Summary
Filesystem Security Infrastructure
src/fs-security.ts
New module introducing PRIVATE_DIR_MODE (0o700) and PRIVATE_FILE_MODE (0o600) constants with utilities: ensurePrivateDirectory, hardenFilePermissions, and writePrivateTextFile to enforce secure permissions on sensitive directories and files.
Core Configuration Hardening
src/config.ts, src/__tests__/config.test.ts
Refactored core config to use fs-security utilities for directory creation and secure file writes. Tests verify config persistence with permission hardening, merging behavior, and strict file mode restoration (600) after load.
Bridge Configuration & Secret Separation
src/bridge/config.ts, src/bridge/__tests__/config.test.ts
Implemented secret separation mechanism storing gatewayAuthToken and ironclawToken in-memory instead of persisted JSON. Added legacy migration: old plaintext tokens are loaded, moved to sessionSecrets, and removed from bridge.json on rewrite. New utilities stripSecrets, loadBridgeFileConfig, recordSessionSecrets handle the flow.
Bridge Wizard & API Client
src/bridge/wizard.ts, src/bridge/__tests__/api-client.test.ts
Updated gateway and IronClaw token prompts to indicate env-only behavior. Tokens are now written to process.env (GATEWAY_AUTH_TOKEN, IRONCLAW_TOKEN) instead of bridge.json. Test refactored to obtain auth token from environment variable.
Initialization & Utilities
src/doctor.ts, src/synapse-db.ts, src/onboarding.ts
Refactored to use ensurePrivateDirectory and writePrivateTextFile for secure directory/file creation. Gateway token handling in onboarding now sets process.env.GATEWAY_AUTH_TOKEN for env-only persistence.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant Config as Bridge Config Module
    participant File as FileSystem
    participant Secrets as SessionSecrets (Memory)
    participant Env as Environment Variables

    App->>Config: getBridgeConfig()
    Config->>File: loadBridgeFileConfig(bridge.json)
    File-->>Config: {fileConfig, legacySecrets, hadLegacySecrets}
    
    alt Legacy Secrets Present
        Config->>Secrets: recordSessionSecrets(legacySecrets)
        Secrets-->>Config: sessionSecrets updated
        Config->>Config: stripSecrets(fileConfig)
        Config->>File: writePrivateTextFile(bridge.json, sanitized)
        File-->>Config: written with 0o600
    end
    
    Config->>Config: merge DEFAULT_CONFIG + fileConfig
    Config->>Env: check GATEWAY_AUTH_TOKEN, IRONCLAW_TOKEN
    Env-->>Config: env values (if set)
    Config->>Secrets: restore sessionSecrets if no env var
    Secrets-->>Config: in-memory secrets applied
    Config-->>App: merged BridgeConfig with restored secrets
Loading
sequenceDiagram
    participant App as Application
    participant Config as Bridge Config Module
    participant Secrets as SessionSecrets (Memory)
    participant File as FileSystem

    App->>Config: saveBridgeConfig(partialConfig)
    Config->>File: loadBridgeFileConfig(bridge.json)
    File-->>Config: {fileConfig, legacySecrets}
    
    Config->>Secrets: recordSessionSecrets(legacySecrets)
    Secrets-->>Config: sessionSecrets accumulated
    
    Config->>Config: stripSecrets(partialConfig)
    Config->>Config: merge sanitized with existing fileConfig
    Config->>File: ensurePrivateDirectory(~/.synapse)
    File-->>Config: directory created with 0o700
    
    Config->>File: writePrivateTextFile(bridge.json, mergedConfig)
    File-->>Config: written with 0o600
    Config-->>App: config persisted (secrets excluded)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested reviewers

  • serrrfirat

Poem

🐰 A rabbit hops through code so fine,
With secrets tucked in env-time,
Permissions hardened, seven-zero-zero,
No plaintext tokens—this rabbit's a hero!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly reflects the main change: hardening synapse config permissions and removing plaintext bridge tokens from persisted config.
Linked Issues check ✅ Passed The PR fulfills all objectives from #46: removes plaintext tokens from persisted config, requires env-only secrets, ensures restrictive directory/file permissions (0700/0600), and includes comprehensive tests.
Out of Scope Changes check ✅ Passed All changes align with the stated objectives of #46. Tests and implementation focus on token removal and permission hardening; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @altantutar, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the security posture of the application by preventing the persistence of sensitive bridge authentication tokens in configuration files. It introduces a robust file system security layer to ensure that configuration directories and files have appropriately restrictive permissions, protecting user data from unauthorized access. Additionally, it includes a migration path for existing installations to remove legacy plaintext tokens, streamlining the transition to a more secure configuration management approach.

Highlights

  • Enhanced Security for Bridge Tokens: Bridge authentication tokens (GATEWAY_AUTH_TOKEN, IRONCLAW_TOKEN) are no longer persisted in bridge.json and are now handled as environment-only variables, improving security by preventing plaintext storage.
  • Legacy Token Migration: Existing plaintext tokens in bridge.json are automatically migrated out of the file upon read, ensuring that sensitive data is removed from persistent storage.
  • Configuration File Permission Hardening: The ~/.synapse directory and its configuration files (config.json, bridge.json) now enforce restrictive POSIX permissions (0o700 for directories, 0o600 for files) to limit access.
  • Updated User Messaging: Setup and onboarding wizards have been updated to inform users that bridge tokens are now environment-only and will not be stored in bridge.json.
  • Comprehensive Testing: New and adjusted tests validate the secret persistence behavior, permission hardening, and legacy token migration processes.
Changelog
  • src/tests/config.test.ts
    • Added tests to verify that saveConfig and getConfig correctly harden permissions for the main configuration directory and file.
    • Introduced a POSIX_PERMISSIONS_SUPPORTED check to conditionally run permission-related tests.
  • src/bridge/tests/api-client.test.ts
    • Modified the test for bearer token headers to retrieve GATEWAY_AUTH_TOKEN from process.env instead of a persisted config file, aligning with the new security model.
  • src/bridge/tests/config.test.ts
    • Added statSync and mode helper function for permission checks.
    • Added a test to ensure saveBridgeConfig hardens config directory and file permissions.
    • Updated gatewayAuthToken value in a test to do-not-persist to reflect its new non-persistent nature.
    • Added assertions to confirm that gatewayAuthToken is undefined in the persisted config after saving.
    • Added a new test case to verify that legacy plaintext token fields (gatewayAuthToken, ironclawToken) are removed from bridge.json on read and not re-persisted.
  • src/bridge/config.ts
    • Imported ensurePrivateDirectory, hardenFilePermissions, and writePrivateTextFile from ../fs-security.js.
    • Added comments clarifying that sensitive tokens are env-only and not persisted.
    • Introduced BridgeSecrets type and sessionSecrets to temporarily hold tokens.
    • Implemented stripSecrets function to separate sensitive tokens from the main configuration object.
    • Created loadBridgeFileConfig to handle reading the bridge config file, hardening permissions, and extracting legacy secrets.
    • Added recordSessionSecrets to store extracted secrets in sessionSecrets.
    • Modified getBridgeConfig to use loadBridgeFileConfig, handle legacy secret migration, and prioritize sessionSecrets if environment variables are not set.
    • Refactored saveBridgeConfig to use ensurePrivateDirectory and writePrivateTextFile, and to strip secrets from the incoming configuration before saving.
  • src/bridge/wizard.ts
    • Updated wizard prompts for gatewayAuthToken and ironclawToken to indicate they are optional and env-only, not stored in bridge.json.
    • Modified the token handling logic during wizard completion to set GATEWAY_AUTH_TOKEN and IRONCLAW_TOKEN directly as environment variables instead of saving them to the config object.
    • Added summary lines to the wizard output to confirm that gateway and IronClaw tokens are handled as env-only.
  • src/config.ts
    • Imported ensurePrivateDirectory, hardenFilePermissions, and writePrivateTextFile from ../fs-security.js.
    • Added hardenFilePermissions call within getConfig to ensure the main config file's permissions are hardened on read.
    • Replaced direct mkdirSync and writeFileSync calls in saveConfig with ensurePrivateDirectory and writePrivateTextFile for secure file operations.
  • src/doctor.ts
    • Imported ensurePrivateDirectory, hardenFilePermissions, and writePrivateTextFile from ../fs-security.js.
    • Replaced direct mkdirSync calls with ensurePrivateDirectory for creating the Synapse directory and database directory.
    • Added hardenFilePermissions call within checkConfig to ensure the config file's permissions are hardened.
    • Replaced direct writeFileSync calls with writePrivateTextFile when creating or replacing the config file.
  • src/fs-security.ts
    • Added a new file to encapsulate file system security utilities.
    • Defined PRIVATE_DIR_MODE (0o700) and PRIVATE_FILE_MODE (0o600) constants.
    • Implemented tryChmod for best-effort permission changes, handling unsupported platforms.
    • Provided ensurePrivateDirectory to create directories with restrictive permissions or harden existing ones.
    • Implemented hardenFilePermissions to set restrictive permissions on existing files.
    • Created writePrivateTextFile to write content to a file, ensuring its parent directory is private and the file itself has restrictive permissions.
  • src/onboarding.ts
    • Updated onboarding wizard prompts for gatewayAuthToken to indicate it is optional and env-only, not stored in bridge.json.
    • Modified the configureOpenClawBridge function to set GATEWAY_AUTH_TOKEN as an environment variable instead of including it in the config object passed to saveBridgeConfig.
  • src/synapse-db.ts
    • Removed mkdirSync and existsSync imports.
    • Imported ensurePrivateDirectory from ../fs-security.js.
    • Replaced the manual existsSync and mkdirSync logic in ensureSynapseDir with a call to ensurePrivateDirectory.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request significantly improves the security of the application by removing plaintext bridge tokens from configuration files and hardening file permissions for configuration artifacts. Secrets are now handled via environment variables, and a one-time migration is in place to remove legacy tokens from existing bridge.json files. The permissions for the ~/.synapse directory and its configuration files are now restricted to the owner (0700 for the directory, 0600 for files). The changes are well-supported by new and updated tests, and the user-facing setup wizards have been updated to reflect the new secret handling process. Overall, this is a great set of changes that enhances the security posture of the application. I have one suggestion to further improve the security hardening.

Comment thread src/synapse-db.ts
if (!existsSync(SYNAPSE_DIR)) {
mkdirSync(SYNAPSE_DIR, { recursive: true });
}
ensurePrivateDirectory(SYNAPSE_DIR);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Great job on hardening the permissions for the Synapse directory. To further improve security, you should also consider hardening the permissions of the SQLite database file itself. The directory is now 0700, but the database file will be created with default permissions (subject to umask). Since the database can contain sensitive user activity data, it should be protected with 0600 permissions, just like the configuration files.

You could use the new hardenFilePermissions function from fs-security.ts for this. A good place to add this would be in getSynapseDb() right after the database object is created:

import { ensurePrivateDirectory, hardenFilePermissions } from './fs-security.js';
// ...

export function getSynapseDb(): Database {
  ensureSynapseDir();
  const db = new Database(SYNAPSE_DB_PATH);
  hardenFilePermissions(SYNAPSE_DB_PATH); // Harden DB file perms
  initializeTables(db);
  return db;
}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/bridge/__tests__/config.test.ts (1)

129-151: ⚠️ Potential issue | 🟡 Minor

Isolate env overrides to avoid flaky token assertions.

If GATEWAY_AUTH_TOKEN / IRONCLAW_TOKEN are set in the environment, these tests will fail because getBridgeConfig() prioritizes env vars. Clear and restore them within the tests.

🛠️ Suggested fix
   test('saveBridgeConfig partial update preserves existing config fields', () => {
     saveBridgeConfig({
       enabled: true,
       openclawEnabled: true,
       toolsEnabled: true,
       apiPort: 49001,
       gatewayAuthToken: 'do-not-persist',
     });
 
     // Simulate `synapse bridge disable`
     saveBridgeConfig({ enabled: false });
 
-    const config = getBridgeConfig();
+    const prevGateway = process.env.GATEWAY_AUTH_TOKEN;
+    const prevIronclaw = process.env.IRONCLAW_TOKEN;
+    delete process.env.GATEWAY_AUTH_TOKEN;
+    delete process.env.IRONCLAW_TOKEN;
+    const config = getBridgeConfig();
+    if (prevGateway !== undefined) process.env.GATEWAY_AUTH_TOKEN = prevGateway;
+    if (prevIronclaw !== undefined) process.env.IRONCLAW_TOKEN = prevIronclaw;
     expect(config.enabled).toBe(false);
     expect(config.openclawEnabled).toBe(true);
     expect(config.toolsEnabled).toBe(true);
     expect(config.apiPort).toBe(49001);
     expect(config.gatewayAuthToken).toBeUndefined();
 
     const raw = readFileSync(CONFIG_PATH, 'utf-8');
     const parsed = JSON.parse(raw);
     expect(parsed.gatewayAuthToken).toBeUndefined();
   });
 
   test('legacy plaintext token fields are removed from bridge.json on read', () => {
     const legacyConfig = {
       enabled: true,
       gatewayAuthToken: 'legacy-gateway-token',
       ironclawToken: 'legacy-ironclaw-token',
       apiPort: 49999,
     };
 
     writeFileSync(CONFIG_PATH, JSON.stringify(legacyConfig), 'utf-8');
-    const config = getBridgeConfig();
+    const prevGateway = process.env.GATEWAY_AUTH_TOKEN;
+    const prevIronclaw = process.env.IRONCLAW_TOKEN;
+    delete process.env.GATEWAY_AUTH_TOKEN;
+    delete process.env.IRONCLAW_TOKEN;
+    const config = getBridgeConfig();
+    if (prevGateway !== undefined) process.env.GATEWAY_AUTH_TOKEN = prevGateway;
+    if (prevIronclaw !== undefined) process.env.IRONCLAW_TOKEN = prevIronclaw;
 
     expect(config.gatewayAuthToken).toBe('legacy-gateway-token');
     expect(config.ironclawToken).toBe('legacy-ironclaw-token');
     expect(config.apiPort).toBe(49999);

Also applies to: 185-203

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/bridge/__tests__/config.test.ts` around lines 129 - 151, The test relies
on getBridgeConfig() which reads GATEWAY_AUTH_TOKEN / IRONCLAW_TOKEN from
process.env, causing flakiness; modify the test(s) that call
saveBridgeConfig/getBridgeConfig (including the block around lines 185-203) to
save the original process.env.GATEWAY_AUTH_TOKEN and process.env.IRONCLAW_TOKEN,
delete those env entries before running the assertions, and restore the original
values in a finally/after step so the environment is isolated for the test and
restored afterwards.
src/config.ts (1)

77-83: ⚠️ Potential issue | 🟠 Major

Harden the config directory on read as well.

getConfig() only hardens the file. If the directory already exists with permissive mode, it stays open unless saveConfig() or doctor runs. It’s safer to ensure the directory permissions whenever reading.

🔒 Suggested fix
 export function getConfig(): SynapseConfig {
   if (!existsSync(CONFIG_PATH)) {
     return DEFAULT_CONFIG;
   }
 
+  ensurePrivateDirectory(CONFIG_DIR);
   hardenFilePermissions(CONFIG_PATH);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config.ts` around lines 77 - 83, getConfig() currently only calls
hardenFilePermissions(CONFIG_PATH) for the file, leaving the containing
directory potentially permissive; update getConfig() to also harden the config
directory on read by invoking the directory-permissions hardening (e.g., call
hardenFilePermissions(path.dirname(CONFIG_PATH)) or add/rename to a
hardenDirPermissions utility) before returning DEFAULT_CONFIG or reading the
file so directory permissions are enforced whenever the config is accessed
(similar to how saveConfig() or doctor would do).
src/bridge/wizard.ts (1)

149-173: ⚠️ Potential issue | 🟡 Minor

Use env vars when deciding to warn about unauthenticated 0.0.0.0 binding.

Line 171 only checks the prompt value; if GATEWAY_AUTH_TOKEN is already set in the environment, the warning is a false positive. Consider including process.env.GATEWAY_AUTH_TOKEN in the check.

🛠️ Suggested fix
-    if (apiHost === '0.0.0.0' && !openclawAdvanced.gatewayAuthToken) {
+    const hasGatewayToken =
+      Boolean(openclawAdvanced.gatewayAuthToken || process.env.GATEWAY_AUTH_TOKEN);
+    if (apiHost === '0.0.0.0' && !hasGatewayToken) {
       p.log.warn('API server will be network-accessible without authentication.\n  Set a gateway auth token to secure it.');
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/bridge/wizard.ts` around lines 149 - 173, The warning about an
unauthenticated 0.0.0.0 binding currently checks only
openclawAdvanced.gatewayAuthToken; update the logic to consider the environment
variable as well by treating process.env.GATEWAY_AUTH_TOKEN as a valid token
when deciding whether to warn. In practice, ensure gatewayAuthToken is set from
openclawAdvanced.gatewayAuthToken or process.env.GATEWAY_AUTH_TOKEN before the
check (references: openclawAdvanced, gatewayAuthToken, apiHost,
process.env.GATEWAY_AUTH_TOKEN) and change the conditional to only warn when
apiHost === '0.0.0.0' && !gatewayAuthToken (or &&
!process.env.GATEWAY_AUTH_TOKEN if you prefer to check env directly).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/__tests__/config.test.ts`:
- Around line 24-30: The test teardown in afterAll (using originalConfig,
CONFIG_DIR, CONFIG_PATH) can leave the restored config file world-readable;
update the restore path to ensure secure permissions by either passing a mode
when writing (e.g., writeFileSync(CONFIG_PATH, originalConfig, { encoding:
'utf-8', mode: 0o600 })) and/or calling chmodSync(CONFIG_PATH, 0o600)
immediately after writeFileSync, and also ensure the else branch that leaves an
existing CONFIG_PATH preserves/sets secure permissions via
chmodSync(CONFIG_PATH, 0o600).

In `@src/bridge/__tests__/api-client.test.ts`:
- Around line 55-62: The test mutates process.env.GATEWAY_AUTH_TOKEN without
preserving existing state—capture the original value before setting (e.g., const
original = process.env.GATEWAY_AUTH_TOKEN), set process.env.GATEWAY_AUTH_TOKEN =
'abc123' for the test, then in the finally block restore it (if original is
undefined delete the env var, otherwise restore the original string); apply this
change around the getHeaders() assertions in the api-client.test.ts test that
references getHeaders to avoid clobbering a developer's environment or causing
order-dependent tests.

---

Outside diff comments:
In `@src/bridge/__tests__/config.test.ts`:
- Around line 129-151: The test relies on getBridgeConfig() which reads
GATEWAY_AUTH_TOKEN / IRONCLAW_TOKEN from process.env, causing flakiness; modify
the test(s) that call saveBridgeConfig/getBridgeConfig (including the block
around lines 185-203) to save the original process.env.GATEWAY_AUTH_TOKEN and
process.env.IRONCLAW_TOKEN, delete those env entries before running the
assertions, and restore the original values in a finally/after step so the
environment is isolated for the test and restored afterwards.

In `@src/bridge/wizard.ts`:
- Around line 149-173: The warning about an unauthenticated 0.0.0.0 binding
currently checks only openclawAdvanced.gatewayAuthToken; update the logic to
consider the environment variable as well by treating
process.env.GATEWAY_AUTH_TOKEN as a valid token when deciding whether to warn.
In practice, ensure gatewayAuthToken is set from
openclawAdvanced.gatewayAuthToken or process.env.GATEWAY_AUTH_TOKEN before the
check (references: openclawAdvanced, gatewayAuthToken, apiHost,
process.env.GATEWAY_AUTH_TOKEN) and change the conditional to only warn when
apiHost === '0.0.0.0' && !gatewayAuthToken (or &&
!process.env.GATEWAY_AUTH_TOKEN if you prefer to check env directly).

In `@src/config.ts`:
- Around line 77-83: getConfig() currently only calls
hardenFilePermissions(CONFIG_PATH) for the file, leaving the containing
directory potentially permissive; update getConfig() to also harden the config
directory on read by invoking the directory-permissions hardening (e.g., call
hardenFilePermissions(path.dirname(CONFIG_PATH)) or add/rename to a
hardenDirPermissions utility) before returning DEFAULT_CONFIG or reading the
file so directory permissions are enforced whenever the config is accessed
(similar to how saveConfig() or doctor would do).

Comment on lines +24 to +30
afterAll(() => {
if (originalConfig !== null) {
mkdirSync(CONFIG_DIR, { recursive: true });
writeFileSync(CONFIG_PATH, originalConfig, 'utf-8');
} else if (existsSync(CONFIG_PATH)) {
rmSync(CONFIG_PATH);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Restore secure permissions when writing back the original config.

writeFileSync may recreate the file with a default umask (often 0644), which can leave the user’s real config less secure after tests.

🛠️ Suggested fix
   afterAll(() => {
     if (originalConfig !== null) {
       mkdirSync(CONFIG_DIR, { recursive: true });
       writeFileSync(CONFIG_PATH, originalConfig, 'utf-8');
+      if (POSIX_PERMISSIONS_SUPPORTED) {
+        chmodSync(CONFIG_PATH, 0o600);
+      }
     } else if (existsSync(CONFIG_PATH)) {
       rmSync(CONFIG_PATH);
     }
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/config.test.ts` around lines 24 - 30, The test teardown in
afterAll (using originalConfig, CONFIG_DIR, CONFIG_PATH) can leave the restored
config file world-readable; update the restore path to ensure secure permissions
by either passing a mode when writing (e.g., writeFileSync(CONFIG_PATH,
originalConfig, { encoding: 'utf-8', mode: 0o600 })) and/or calling
chmodSync(CONFIG_PATH, 0o600) immediately after writeFileSync, and also ensure
the else branch that leaves an existing CONFIG_PATH preserves/sets secure
permissions via chmodSync(CONFIG_PATH, 0o600).

Comment on lines +55 to +62
process.env.GATEWAY_AUTH_TOKEN = 'abc123';
try {
const headers = getHeaders();
expect(headers.Authorization).toBe('Bearer abc123');
expect(headers['Content-Type']).toBe('application/json');
} finally {
delete process.env.GATEWAY_AUTH_TOKEN;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Preserve any pre-existing env var to avoid test side effects.

Deleting the env var unconditionally can clobber a developer’s existing GATEWAY_AUTH_TOKEN and make tests order-dependent.

🛠️ Suggested fix
-    process.env.GATEWAY_AUTH_TOKEN = 'abc123';
+    const previousToken = process.env.GATEWAY_AUTH_TOKEN;
+    process.env.GATEWAY_AUTH_TOKEN = 'abc123';
     try {
       const headers = getHeaders();
       expect(headers.Authorization).toBe('Bearer abc123');
       expect(headers['Content-Type']).toBe('application/json');
     } finally {
-      delete process.env.GATEWAY_AUTH_TOKEN;
+      if (previousToken === undefined) {
+        delete process.env.GATEWAY_AUTH_TOKEN;
+      } else {
+        process.env.GATEWAY_AUTH_TOKEN = previousToken;
+      }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
process.env.GATEWAY_AUTH_TOKEN = 'abc123';
try {
const headers = getHeaders();
expect(headers.Authorization).toBe('Bearer abc123');
expect(headers['Content-Type']).toBe('application/json');
} finally {
delete process.env.GATEWAY_AUTH_TOKEN;
}
const previousToken = process.env.GATEWAY_AUTH_TOKEN;
process.env.GATEWAY_AUTH_TOKEN = 'abc123';
try {
const headers = getHeaders();
expect(headers.Authorization).toBe('Bearer abc123');
expect(headers['Content-Type']).toBe('application/json');
} finally {
if (previousToken === undefined) {
delete process.env.GATEWAY_AUTH_TOKEN;
} else {
process.env.GATEWAY_AUTH_TOKEN = previousToken;
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/bridge/__tests__/api-client.test.ts` around lines 55 - 62, The test
mutates process.env.GATEWAY_AUTH_TOKEN without preserving existing state—capture
the original value before setting (e.g., const original =
process.env.GATEWAY_AUTH_TOKEN), set process.env.GATEWAY_AUTH_TOKEN = 'abc123'
for the test, then in the finally block restore it (if original is undefined
delete the env var, otherwise restore the original string); apply this change
around the getHeaders() assertions in the api-client.test.ts test that
references getHeaders to avoid clobbering a developer's environment or causing
order-dependent tests.

import { homedir } from 'os';
import { getConfig, saveConfig } from '../config.js';

const CONFIG_DIR = join(homedir(), '.synapse');

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are touching the real ~/.synapse/config.json path in the user home directory. That makes them stateful/flaky and risks modifying user config if the run is interrupted before afterAll cleanup.\n\nCan we isolate with a temp HOME (or injectable config path) per test run? That would keep permission checks valid while avoiding side effects on real local config.

import { homedir } from 'os';
import { getBridgeConfig, saveBridgeConfig } from '../config.js';

const CONFIG_DIR = join(homedir(), '.synapse');

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same isolation issue here: this suite writes to the real ~/.synapse/bridge.json. If tests fail/abort, local bridge config can be left modified, and parallel runs can interfere with each other.\n\nPlease consider temp HOME / temporary config root injection to make this deterministic and side-effect free.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

move bridge tokens out of plaintext config and harden local file permissions

2 participants