Skip to content

Encryption key generation failure in web export#607

Open
ikostan wants to merge 102 commits into
mainfrom
encryption-key-generation-failure-in-web-export
Open

Encryption key generation failure in web export#607
ikostan wants to merge 102 commits into
mainfrom
encryption-key-generation-failure-in-web-export

Conversation

@ikostan
Copy link
Copy Markdown
Owner

@ikostan ikostan commented May 12, 2026


name: Default Pull Request Template
about: Suggesting changes to SkyLockAssault
title: ''
labels: ''
assignees: ''

Description

image

What does this PR do? (e.g., "Fixes player jump physics in level 2" or "Adds
new enemy AI script")

Related Issue

Closes #ISSUE_NUMBER (if applicable)

Changes

  • List key changes here (e.g., "Updated Jump.gd to use Godot 4.4's new Tween
    system")
  • Any breaking changes? (e.g., "Deprecated old signal; migrate to new one")

Testing

  • Ran the game in Godot v4.5 editor—describe what you tested (e.g., "Jump
    works on Win10 with 60 FPS")
  • Any new unit tests added? (Link to test scene if yes)
  • Screenshots/GIFs if UI-related: (Attach below)

Checklist

  • Code follows Godot style guide (e.g., snake_case for variables)
  • No console errors in editor/output
  • Ready for review!

Additional Notes

Anything else? (e.g., "Tested on Win10 64-bit; needs Linux validation")

Summary by Sourcery

Switch encryption salt handling from project settings to CI-injected GDScript bytecode, hardening web exports and improving failure recovery.

New Features:

  • Inject a production encryption salt directly into GDScript via a reusable CI bash script using a safe placeholder in globals.gd.
  • Add a local shell script to simulate the CI/CD web export pipeline and serve the built game for manual validation.

Bug Fixes:

  • Prevent web exports from crashing due to JavaScriptBridge and unique ID calls during encryption key generation.
  • Ensure corrupted or orphaned encrypted save files are automatically deleted so the game can recover with fresh defaults.
  • Avoid headless export crashes by disabling editor plugins in the deployment workflow and relevant local scripts.
  • Fix test flakiness by cleaning up shared settings files across multiple GUT test suites.

Enhancements:

  • Refactor encryption key generation to use a bytecode-embedded salt and SHA-256 key, simplifying configuration and removing dependency on ProjectSettings.
  • Update encryption-related tests to validate the new bytecode-based key generation and binary encryption checks instead of plaintext fallbacks.
  • Centralize salt injection logic into a single bash script reused by both CI and Python tests.

CI:

  • Replace the inlined awk-based salt injection in the deploy workflow with a call to the shared inject_salt.sh script and add a step to disable editor plugins before export.
  • Bump pinned versions of markdownlint-cli2-action and release-drafter actions to newer SHAs.

Tests:

  • Rewrite salt injection tests to run against the shared injection script and the new GDScript placeholder mechanism.
  • Adjust encryption tests to assert valid SHA-256 keys and encrypted file detection, and simplify logging tests to no longer depend on ProjectSettings.

Summary by Sourcery

Switch save encryption to use a CI-injected GDScript salt and reinforce web exports and tests against crashes and corrupted state.

New Features:

  • Introduce a reusable bash script and Python helper to inject production salts and a dedicated 'ci' feature flag into Godot export configs and GDScript.
  • Add a local shell harness to simulate the CI web export pipeline end-to-end and serve the built game for manual browser validation.

Bug Fixes:

  • Prevent web encryption key generation from crashing headless or web exports by avoiding JavaScript bridge calls and editor-only dependencies.
  • Ensure corrupted or orphaned encrypted config files are automatically deleted and regenerated so the game can recover cleanly.
  • Avoid CI and local headless crashes by disabling editor plugins during exports and cleaning up shared settings files between GUT test suites.
  • Stabilize Playwright browser flows by increasing timeouts and improving the web server used for HTML5 builds.

Enhancements:

  • Refine encryption key generation to use a bytecode-embedded salt, remove ProjectSettings coupling, and enforce SHA-256-style keys.
  • Harden config loading with more precise logging and guarded auto-deletion logic around decryption failures.
  • Extract cross-platform CI utilities (sed wrapper, plugin disabling) for reuse across scripts and workflows.
  • Simplify encryption-related GUT tests to align with the new bytecode salt model and binary encryption checks instead of plaintext fallbacks.

Build:

  • Add a script to inject a dedicated 'ci' feature flag into export_presets so test builds can bypass production security guards safely.
  • Adjust browser test runner scripts to align with the CI salt/flag injection flow and keep the working tree clean after local runs.

CI:

  • Replace inline salt and feature injection logic in workflows with calls to the shared injection scripts and utilities.
  • Expand CI coverage with pytest-based suites for salt injection and 'ci' flag handling, and exclude these from Playwright runs to avoid duplication.
  • Upgrade pinned versions of markdownlint and release-drafter GitHub Actions to newer SHAs.

Tests:

  • Rewrite salt injection and CI flag tests to target the shared scripts and cover edge cases like encoding, malformed configs, and idempotency.
  • Update browser-based audio, navigation, and load-flow tests with configurable timeouts and improved initialization waits.
  • Clean up settings files in several GUT tests to prevent cross-test contamination in CI.

Chores:

  • Bump urllib3 to a newer patch version in requirements to keep dependencies current.

Summary by CodeRabbit

  • Chores

    • Updated pinned GitHub Action versions and bumped urllib3 to 2.7.0.
  • Tests

    • Added a local CI-like script to build a web export, inject a save salt, and serve the site with COOP/COEP headers.
  • Bug Fixes

    • Improved encryption-key selection on web platforms and disabled the AI autonomous agent editor plugin; set a non-empty security save salt.

Review Change Stack

ikostan and others added 10 commits May 9, 2026 21:17
Removed the ai_autonomous_agent entry from the enabled editor plugins in project.godot. Updated globals.gd to use OS.has_feature("web") instead of comparing OS.get_name(), preventing OS.get_unique_id() from crashing on Web builds. Added workspace/test_injection.sh — a local CI/CD simulation script that injects a test salt into project.godot (with backup), runs a Godot web export (expects godot CLI and a 'Web' preset), verifies the injection, packages the export, and starts a Python server that sets COOP/COEP headers for SharedArrayBuffer/thread support. The script includes basic verification output and usage notes for running locally.
Bumps [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action) from 23.0.0 to 23.2.0.
- [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases)
- [Commits](DavidAnson/markdownlint-cli2-action@ce4853d...ded1f94)

---
updated-dependencies:
- dependency-name: DavidAnson/markdownlint-cli2-action
  dependency-version: 23.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
Bumps [release-drafter/release-drafter](https://github.com/release-drafter/release-drafter) from 7.2.0 to 7.3.0.
- [Release notes](https://github.com/release-drafter/release-drafter/releases)
- [Commits](release-drafter/release-drafter@5de9358...c2e2804)

---
updated-dependencies:
- dependency-name: release-drafter/release-drafter
  dependency-version: 7.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
Bumps the pip group with 1 update in the / directory: [urllib3](https://github.com/urllib3/urllib3).


Updates `urllib3` from 2.6.3 to 2.7.0
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](urllib3/urllib3@2.6.3...2.7.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.7.0
  dependency-type: direct:production
  dependency-group: pip
...

Signed-off-by: dependabot[bot] <[email protected]>
…nson/markdownlint-cli2-action-23.2.0

Bump DavidAnson/markdownlint-cli2-action from 23.0.0 to 23.2.0
…e-drafter/release-drafter-7.3.0

Bump release-drafter/release-drafter from 7.2.0 to 7.3.0
Bump urllib3 from 2.6.3 to 2.7.0 in the pip group across 1 directory
@ikostan ikostan added this to the Milestone 18: TBD milestone May 12, 2026
@ikostan ikostan self-assigned this May 12, 2026
@ikostan ikostan added bug Something isn't working setup labels May 12, 2026
@ikostan ikostan linked an issue May 12, 2026 that may be closed by this pull request
@ikostan ikostan added web testing dependencies Pull requests that update a dependency file github actions Pull requests that update GitHub Actions code dependabot github_actions Pull requests that update GitHub Actions code security labels May 12, 2026
@ikostan ikostan moved this to In Progress in Sky Lock Assault Project May 12, 2026
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented May 12, 2026

Reviewer's Guide

Refactors save encryption to use a CI‑injected GDScript salt and SHA‑256 key while hardening web exports, adding CI utilities for salt/feature-flag injection, improving corrupted-save recovery, and stabilizing browser and GUT tests; updates CI workflows and dependencies accordingly.

File-Level Changes

Change Details Files
Switch encryption key generation from ProjectSettings-based salt to a CI-injected GDScript salt and adjust encryption behavior and logging.
  • Change _get_encryption_key to use a placeholder salt string in globals.gd to be replaced by CI instead of reading ProjectSettings
  • Introduce a ci feature flag via OS.has_feature("ci") to exempt automated tests from production security guards
  • Remove JavaScriptBridge-based webdriver detection and avoid OS.get_unique_id crashes on web by guarding with OS.has_feature("web")
  • Tighten empty-salt handling to abort key generation without plaintext fallback and log debug info for test environments
scripts/core/globals.gd
Improve encrypted config loading with auto-recovery for corrupted files while preserving non-corrupt failures.
  • Extend safe_load_config to delete only explicitly corrupted or invalid encrypted files and treat them as first-boot defaults
  • Add detailed logging around deletion success/failure and non-corrupt unreadable files to avoid accidental data loss
scripts/core/globals.gd
Replace the AWK-based salt injector with a reusable bash script plus CI utilities, and add a Python script to inject the ci feature flag into export_presets.cfg.
  • Rewrite .github/scripts/inject_salt.sh to validate inputs, escape secrets for Godot and sed, and delegate in-place edits to a shared sed wrapper
  • Add .github/scripts/ci_utils.sh with cross-platform sedi and disable_editor_plugins helpers used by local and CI scripts
  • Create .github/scripts/inject_ci_flag.py that backs up export_presets.cfg and injects custom_features="ci" for each preset in an idempotent way
.github/scripts/inject_salt.sh
.github/scripts/ci_utils.sh
.github/scripts/inject_ci_flag.py
Update CI workflows and local browser-test pipeline to use the new salt and ci-flag injectors, and to disable editor plugins for headless exports.
  • Modify deploy_to_itch workflow to disable editor plugins via ci_utils and inject the production salt into globals.gd instead of project.godot
  • Modify browser_test workflow to use inject_salt.sh and inject_ci_flag.py, run pytest while ignoring tests/ci, and start a custom HTTP server with COOP/COEP headers
  • Enhance workspace/run_browser_tests.sh to inject a dummy salt and ci flag, export Web_thread_off, restore modified files via git, start a hardened HTTP server, increase Playwright timeout, and skip tests/ci
.github/workflows/deploy_to_itch.yml
.github/workflows/browser_test.yml
workspace/run_browser_tests.sh
Add comprehensive pytest coverage for the salt and ci-flag injection utilities and integrate them into CI.
  • Replace the standalone Python AWK salt test with a pytest-based suite that calls inject_salt.sh with complex secrets, path edge cases, permission errors, and idempotency checks
  • Add tests/ci/conftest.py fixture repo_tmp to create temp dirs inside the repo root with POSIX paths for WSL compatibility
  • Introduce tests/ci/test_ci_flag_injection.py to validate export_presets.cfg modifications, backups, idempotency, multi-preset behavior, CRLF handling, and malformed config safety
  • Update test_ci_scripts workflow to install pytest/playwright and run tests/ci via pytest instead of a custom script
tests/ci/test_salt_injection.py
tests/ci/conftest.py
tests/ci/test_ci_flag_injection.py
.github/workflows/test_ci_scripts.yml
Retune GUT encryption tests to the new bytecode-based key generation and encrypted file detection, and clean up cross-test filesystem state for input-binding tests.
  • Simplify test_encryption_failsafe to assert non-empty SHA-256 keys from ensure_encryption_key and to validate encryption via is_file_encrypted instead of plaintext fallbacks and ProjectSettings salt properties
  • Simplify test_encryption_logging to no longer manage ProjectSettings salt and rely on Globals settings and key cache reset
  • Ensure several input-related GUT tests delete user://settings.cfg before/after runs to avoid interference from encryption tests
test/gut/test_encryption_failsafe.gd
test/gut/test_encryption_logging.gd
test/gut/test_get_pause_binding_label_for_device.gd
test/gut/test_input_remap_button.gd
test/gut/test_deduplication_on_device_switch.gd
Stabilize Playwright/browser tests with longer timeouts and better separation from CI utility tests.
  • Increase Playwright timeouts and replace literal waits with TEST_TIMEOUT in multiple flow tests (audio, back, reset audio, difficulty, load main menu, navigation, clean load, volume sliders, no_error_logs)
  • Ensure browser test invocations ignore tests/ci so infrastructure tests do not run in browser jobs
workspace/run_browser_tests.sh
tests/audio_flow_test.py
tests/back_flow_test.py
tests/reset_audio_flow_test.py
tests/difficulty_flow_test.py
tests/load_main_menu_test.py
tests/navigation_to_audio_test.py
tests/no_error_logs_test.py
tests/validate_clean_load_test.py
tests/volume_sliders_mutes_test.py
.github/workflows/browser_test.yml
Add a local end-to-end CI simulation script for web export and serving with consistent headers.
  • Introduce workspace/test_injection.sh to check dependencies, back up key config files, disable editor plugins, run salt and ci-flag injectors, perform a Web export with progress feedback, optionally patch index.js, and start a local HTTP server with COOP/COEP headers
  • Ensure the script restores modified files on exit and cleans internal backup artifacts
workspace/test_injection.sh
Miscellaneous CI and dependency maintenance.
  • Update markdownlint-cli2 and release-drafter GitHub Actions to newer pinned SHAs
  • Bump urllib3 from 2.6.3 to 2.7.0 in requirements.txt
.github/workflows/lint_readme.yml
.github/workflows/release_drafter.yml
.github/workflows/release_drafter_pr.yml
requirements.txt

Assessment against linked issues

Issue Objective Addressed Explanation
#597 Create a shared, executable shell script (.github/scripts/inject_salt.sh) that encapsulates the salt injection logic previously implemented inline (awk/sed) in deploy_to_itch.yml.
#597 Update the deployment workflow (deploy_to_itch.yml) to remove the inline salt injection logic and instead call the shared inject_salt.sh script with the appropriate arguments and environment (using PRODUCTION_SALT).
#597 Refactor the Python salt injection test (test_salt_injection.py) to remove the embedded AWK script and instead validate behavior by invoking the shared inject_salt.sh via subprocess, ensuring tests exercise the same code as CI.
#600 Harden the web CI/deploy pipeline by moving the save encryption salt from project.godot into CI-injected GDScript bytecode (via a shared inject_salt.sh script) and integrating this into workflows, while disabling editor plugins before headless exports to avoid crashes.
#600 Fix web encryption key generation so it no longer triggers WebAssembly crashes, uses the injected salt instead of ProjectSettings, produces a non-empty SHA-256 key, and supports a CI-only feature flag to bypass production security guards during automated tests.
#600 Implement automatic recovery from corrupted or key-mismatched encrypted config/save files by detecting decryption errors (e.g., ERR_FILE_CORRUPT / Error 16), deleting the bad file, and allowing the game to regenerate clean defaults instead of entering a permanent error loop.

Possibly linked issues

  • #[BUG] Encryption Key Generation Failure in Web Export: PR directly implements the issue’s proposed fixes: bytecode salt injection, JSBridge removal, auto-recovery, and CI/export hardening.
  • #FEATURE Implement Encryption Key Management for Config Files: PR implements and evolves the requested centralized encryption key and CI-managed salt, plus web and CI robustness improvements.
  • #: They address the same bug: web exports losing the injected save_salt and falling back to plaintext saves by redesigning salt injection.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR updates Godot web export salt handling and platform detection, adds a salt-injection and export test script, updates three GitHub Actions action pins, and bumps urllib3 to 2.7.0.

Changes

Web Export Encryption and Salt Management

Layer / File(s) Summary
Godot project configuration and web-aware encryption key
project.godot, scripts/core/globals.gd
Editor plugins list now keeps only GUT; security/save_salt set to "dev_fallback_salt". Encryption key selection changed to if not OS.has_feature("web"):.
Salt injection test and build export pipeline
workspace/test_injection.sh
Adds a cleanup/trap, backs up/restores project.godot, sets GODOT_CMD and PRODUCTION_SALT, escapes and injects the salt into [game] via AWK, runs headless Godot web export, verifies outputs, optionally patches JS, and generates/starts a Python server that adds COOP/COEP headers.

Workflow and Dependency Maintenance

Layer / File(s) Summary
GitHub Actions workflow version updates
.github/workflows/lint_readme.yml, .github/workflows/release_drafter.yml, .github/workflows/release_drafter_pr.yml
Pinned commit SHAs updated for DavidAnson/markdownlint-cli2-action and release-drafter/release-drafter; workflow logic unchanged.
Python dependency updates
requirements.txt
Bumped urllib3 from 2.6.3 to 2.7.0.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested labels

CI/CD

Poem

🐰 I hopped through configs, salt in paw,
I set the web flag check with careful awe,
A script backs up, injects, and builds the site,
Pinned workflows and deps tucked in just right,
The rabbit hums — the export runs tonight.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description is largely a template with checkboxes and includes multiple Sourcery summaries, but lacks specific details about what was actually changed and why in the author's own words. Replace the template placeholders with concrete information: clearly describe the encryption key generation fix, explain why using OS.has_feature('web') is better than OS.get_name() != 'Web', detail the test_injection.sh script purpose, and remove or clarify the auto-generated Sourcery summaries.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Encryption key generation failure in web export' directly summarizes the main bug fix in the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch encryption-key-generation-failure-in-web-export

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • In workspace/test_injection.sh, consider adding a trap or final cleanup step to automatically restore project.godot from project.godot.backup so local runs don't leave the project file permanently modified.
  • The build/export section in workspace/test_injection.sh is duplicated (two "Executing Build & Export Pipeline" headers and multiple mkdir/rm commands); you can simplify this by consolidating the logic into a single block.
  • The salt-escaping pipeline in workspace/test_injection.sh is a bit hard to follow (ESCAPED_SALT, GODOT_ESCAPED, SED_ESCAPED); consider reducing the number of intermediate variables or adding brief comments at each stage to clarify the purpose of each transformation.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In workspace/test_injection.sh, consider adding a trap or final cleanup step to automatically restore project.godot from project.godot.backup so local runs don't leave the project file permanently modified.
- The build/export section in workspace/test_injection.sh is duplicated (two "Executing Build & Export Pipeline" headers and multiple mkdir/rm commands); you can simplify this by consolidating the logic into a single block.
- The salt-escaping pipeline in workspace/test_injection.sh is a bit hard to follow (ESCAPED_SALT, GODOT_ESCAPED, SED_ESCAPED); consider reducing the number of intermediate variables or adding brief comments at each stage to clarify the purpose of each transformation.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@deepsource-io
Copy link
Copy Markdown

deepsource-io Bot commented May 12, 2026

DeepSource Code Review

We reviewed changes in 0328958...2329305 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
Python May 15, 2026 5:14a.m. Review ↗
JavaScript May 15, 2026 5:14a.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
workspace/test_injection.sh (2)

11-11: ⚡ Quick win

Add comment explaining the intentional special character test case.

The raw secret 'T3st_S@lt!_2026#"\' intentionally includes special characters (quotes and backslashes) to test the escaping logic. While syntactically correct, this is non-obvious.

📝 Suggested clarification
-# 1. Hardcoded Secret (Mimicking GitHub Secrets)
-RAW_SECRET='T3st_S@lt!_2026#"\'
+# 1. Hardcoded Secret (Mimicking GitHub Secrets)
+# Note: Intentionally includes special characters (", \, ') to test escaping edge cases
+RAW_SECRET='T3st_S@lt!_2026#"\'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@workspace/test_injection.sh` at line 11, Add an inline comment above the
RAW_SECRET assignment clarifying that the value RAW_SECRET='T3st_S@lt!_2026#"\'
intentionally contains quotes and a backslash to exercise/verify escaping logic
in tests, and that the characters must remain exactly as written (do not
sanitize or change them) so future readers know this is a deliberate test case.

30-37: ⚡ Quick win

Remove redundant GODOT_ESCAPED variable.

Line 34 creates GODOT_ESCAPED with identical logic to ESCAPED_SALT (line 30), then line 35 immediately uses it to create SED_ESCAPED. This adds no value and makes the escaping chain harder to follow.

♻️ Proposed simplification
 # 2. Mimic the GitHub Action Injection Step
 ESCAPED_SALT=$(printf '%s' "$PRODUCTION_SALT" | sed 's/\\/\\\\/g; s/"/\\"/g')
 
 echo "⚙️ Running sed injection script..."
 
-GODOT_ESCAPED=$(printf '%s' "$PRODUCTION_SALT" | sed 's/\\/\\\\/g; s/"/\\"/g')
-SED_ESCAPED=$(printf '%s' "$GODOT_ESCAPED" | sed 's/\\/\\\\/g; s/&/\\&/g; s/|/\\|/g')
+SED_ESCAPED=$(printf '%s' "$ESCAPED_SALT" | sed 's/\\/\\\\/g; s/&/\\&/g; s/|/\\|/g')
 
 sed -i "s|^[[:space:]]*security/save_salt=.*|security/save_salt=\"$SED_ESCAPED\"|g" project.godot
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@workspace/test_injection.sh` around lines 30 - 37, GODOT_ESCAPED is redundant
because it duplicates ESCAPED_SALT; remove the GODOT_ESCAPED assignment and
build SED_ESCAPED directly from ESCAPED_SALT (use ESCAPED_SALT in the printf/sed
chain that creates SED_ESCAPED), then keep the sed -i replacement using
SED_ESCAPED to update security/save_salt in project.godot; ensure variable names
remain ESCAPED_SALT and SED_ESCAPED so the final sed line still references
SED_ESCAPED.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@project.godot`:
- Line 57: Document the breaking change in the PR description and add migration
guidance: explain that changing save_salt to "dev_fallback_salt" will make
previously plaintext dev configs unreadable and advise developers to delete
local user://config.cfg (or equivalent) before running; additionally modify
safe_load_config() to detect legacy plaintext configs (e.g., by checking for
absence of expected salt/version/marker or validating MD5 before decryption) and
implement a graceful migration path or clear error message prompting deletion,
and include a version/check flag written when creating encrypted configs so
future loads can distinguish formats.

In `@workspace/test_injection.sh`:
- Line 144: The simple cd export/web command can fail; update the script in
workspace/test_injection.sh to check the directory change and handle errors
explicitly by testing the result of cd export/web (or verifying the directory
exists beforehand), and on failure print a clear error message and exit non‑zero
so subsequent steps don't run; ensure the error handling is adjacent to the
existing cd export/web invocation so it's easy to locate.

---

Nitpick comments:
In `@workspace/test_injection.sh`:
- Line 11: Add an inline comment above the RAW_SECRET assignment clarifying that
the value RAW_SECRET='T3st_S@lt!_2026#"\' intentionally contains quotes and a
backslash to exercise/verify escaping logic in tests, and that the characters
must remain exactly as written (do not sanitize or change them) so future
readers know this is a deliberate test case.
- Around line 30-37: GODOT_ESCAPED is redundant because it duplicates
ESCAPED_SALT; remove the GODOT_ESCAPED assignment and build SED_ESCAPED directly
from ESCAPED_SALT (use ESCAPED_SALT in the printf/sed chain that creates
SED_ESCAPED), then keep the sed -i replacement using SED_ESCAPED to update
security/save_salt in project.godot; ensure variable names remain ESCAPED_SALT
and SED_ESCAPED so the final sed line still references SED_ESCAPED.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: be56e06d-73ad-4cc8-b46a-da75a129206d

📥 Commits

Reviewing files that changed from the base of the PR and between 0328958 and 4d7d861.

📒 Files selected for processing (7)
  • .github/workflows/lint_readme.yml
  • .github/workflows/release_drafter.yml
  • .github/workflows/release_drafter_pr.yml
  • project.godot
  • requirements.txt
  • scripts/core/globals.gd
  • workspace/test_injection.sh
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-04-28T02:11:45.806Z
Learnt from: ikostan
Repo: ikostan/SkyLockAssault PR: 588
File: .github/workflows/deploy_to_itch.yml:44-56
Timestamp: 2026-04-28T02:11:45.806Z
Learning: When a CI workflow edits Godot's `project.godot` (INI) to inject custom ProjectSettings values, insert the setting key under the correct section header that matches the `game/` (or other) root in the ProjectSettings path. For example, `ProjectSettings.get_setting("game/security/save_salt", ...)` expects the INI entry under `[game]` with key `security/save_salt` (i.e., `[game]` then `security/save_salt=...`), not under `[application]`. Otherwise the lookup will fall back to the default value at runtime.

Applied to files:

  • .github/workflows/release_drafter_pr.yml
  • .github/workflows/release_drafter.yml
  • .github/workflows/lint_readme.yml
🪛 GitHub Actions: Pull Request Pipeline / 1_GUT Unit Tests _ unit-test.txt
scripts/core/globals.gd

[error] 520-520: safe_load_config triggered ERR_FILE_CORRUPT due to encrypted file MD5 mismatch.

🪛 Shellcheck (0.11.0)
workspace/test_injection.sh

[info] 11-11: Want to escape a single quote? echo 'This is how it'''s done'.

(SC1003)


[warning] 144-144: Use 'cd ... || exit' or 'cd ... || return' in case cd fails.

(SC2164)

🔇 Additional comments (8)
requirements.txt (1)

34-34: LGTM!

.github/workflows/release_drafter_pr.yml (1)

19-19: LGTM!

.github/workflows/release_drafter.yml (1)

29-29: LGTM!

.github/workflows/lint_readme.yml (1)

24-24: LGTM!

project.godot (1)

53-53: LGTM!

workspace/test_injection.sh (2)

46-79: LGTM!


88-116: LGTM!

scripts/core/globals.gd (1)

483-483: ⚡ Quick win

Remove the unsupported claim about changing from OS.get_name() to OS.has_feature().

The encryption key function in _get_encryption_key() uses OS.has_feature("web"), but git history shows no evidence that it ever used OS.get_name() == "Web". The concern about platform detection equivalence is valid in principle, but the specific claim about this particular change cannot be verified.

However, the existing code already includes safeguards for this scenario: it detects legacy plaintext files (via is_legacy = true in safe_load_config()) and logs clear warnings when decryption fails. The migration path is documented through these error messages and the fallback logic.

If platform detection semantics differ between OS.get_name() and OS.has_feature(), users with existing encrypted configs will see the decryption error message and can manually migrate by decrypting with the old key. Consider adding documentation about this potential compatibility issue or adding a tool/script for safe key rotation if this is a known risk.

			> Likely an incorrect or invalid review comment.

Comment thread project.godot
Comment thread workspace/test_injection.sh Outdated
The cd command can fail if the directory doesn't exist or permissions are denied. While the export should have created export/web, it's safer to handle potential failures explicitly.
ikostan added 2 commits May 14, 2026 17:09
Python and Playwright tests (audio_flow_test.py and test_ci_flag_injection.py) are fully up to date and require no changes. The Playwright assertions don't explicitly hunt for the IS_AUTOMATED_TEST log, so dropping it to DEBUG won't break anything.

However, workspace/test_injection.sh represents a gap. Because it acts as your local CI/CD simulation, it needs to perfectly mirror the actual deployment pipeline. Right now, it handles the salt injection but entirely skips the new ci flag injection step we just built.

To keep everything synchronized, we need to add the Python script execution to the simulation and ensure export_presets.cfg is properly backed up and restored so your local repository stays clean.
@ikostan ikostan linked an issue May 15, 2026 that may be closed by this pull request
ikostan and others added 2 commits May 14, 2026 17:26
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
@ikostan
Copy link
Copy Markdown
Owner Author

ikostan commented May 15, 2026

  • In .github/scripts/inject_ci_flag.py, sys.stdout.encoding can be None in some environments, so guard it before calling .lower() (e.g., enc = sys.stdout.encoding or '') to avoid an AttributeError.

Addressed

@ikostan
Copy link
Copy Markdown
Owner Author

ikostan commented May 15, 2026

  • The sed call in .github/workflows/browser_test.yml (sed -i 's/^enabled=PackedStringArray.*/enabled=PackedStringArray()/' project.godot) is not scoped to the [editor_plugins] section like the deploy workflow version; consider using the same section-bounded pattern to avoid accidentally touching other enabled= keys.

The Unscoped sed Call (browser_test.yml)

In test_injection.sh, we correctly scoped the plugin-disabling command to just the [editor_plugins] block. But in our GitHub Actions workflow, it is still a greedy regex that will disable any array named enabled= anywhere in project.godot.

The Fix

Update lines 25-26 in .github/workflows/browser_test.yml to match the scoped version from our shell script:

      - name: "Disable Editor Plugins (Prevent Headless Crash)"
        run: |
          # Prevents Signal 11 crashes during headless export on Ubuntu runners
          sed -i '/^\[editor_plugins\]/,/^\[/ s/^enabled=PackedStringArray.*/enabled=PackedStringArray()/' project.godot

In test_injection.sh, you correctly scoped the plugin-disabling command to just the [editor_plugins] block. But in your GitHub Actions workflow, it is still a greedy regex that will disable any array named enabled= anywhere in project.godot.

The Fix: Update lines 25-26 in .github/workflows/browser_test.yml to match the scoped version from your shell script
@ikostan
Copy link
Copy Markdown
Owner Author

ikostan commented May 15, 2026

  • Both inject_salt.sh and workspace/test_injection.sh rely on sed -i with GNU-style semantics, which will fail on macOS/BSD sed; if you expect these scripts to be run locally on macOS, consider adding a portable sed wrapper or using the -i '' form when uname reports Darwin.

macOS / BSD sed compatibility (test_injection.sh & inject_salt.sh)

Both scripts are currently hardcoded to use sed -i "...", which is standard for Linux/GNU but will fail with an error on macOS (which requires sed -i '' "..."). To make these scripts portable for any developer testing locally, we need to add a small wrapper.

The Fix for .github/scripts/inject_salt.sh:

Add the sedi wrapper and replace the final sed -i call at the bottom of the file (lines 26-27):

# 2. Escape for sed replacement string
SED_ESCAPED=$(printf '%s' "$GODOT_ESCAPED" | sed 's/\\/\\\\/g; s/&/\\&/g; s/|/\\|/g')

# Cross-platform sed for in-place editing (macOS vs Linux)
sedi() {
  if [ "$(uname)" = "Darwin" ]; then
    sed -i '' "$@"
  else
    sed -i "$@"
  fi
}

# 3. Replace the safe placeholder with the real secret
sedi "s|\"CI_INJECT_SALT_HERE\"|\"$SED_ESCAPED\"|g" "$TARGET_FILE"

The Fix for test_injection.sh:

Add the same wrapper below your variable declarations (around line 21) and update the plugin command:

GODOT_CMD="godot"
RAW_SECRET='T3st_S@lt!_2026#"\'
export PRODUCTION_SALT="$RAW_SECRET"

# Cross-platform sed for in-place editing (macOS vs Linux)
sedi() {
  if [ "$(uname)" = "Darwin" ]; then
    sed -i '' "$@"
  else
    sed -i "$@"
  fi
}

echo "=========================================="
echo " Starting Local CI/CD Simulation"
echo "=========================================="

# ... [skip to line 41] ...

echo "🔌 Disabling editor plugins (GUT) to prevent headless crashes..."
sedi '/^\[editor_plugins\]/,/^\[/ s/^enabled=PackedStringArray.*/enabled=PackedStringArray()/' project.godot

ikostan added 2 commits May 14, 2026 17:54
macOS / BSD sed compatibility (test_injection.sh & inject_salt.sh)
Both scripts are currently hardcoded to use sed -i "...", which is standard for Linux/GNU but will fail with an error on macOS (which requires sed -i '' "..."). To make these scripts portable for any developer testing locally, we need to add a small wrapper.
…y secret to reduce brittleness

suggestion (testing): Relax assertion on exact error message for empty secret to reduce brittleness

Asserting on the exact string in stdout makes this test fragile to harmless wording changes in the script. Instead, rely on behavior: assert the non‑zero return code, that the file content remains unchanged, and optionally that some error output is produced, without checking this specific phrase.
@ikostan
Copy link
Copy Markdown
Owner Author

ikostan commented May 15, 2026

  • In .github/scripts/inject_ci_flag.py, sys.stdout.encoding can be None in some environments; calling .lower() on it will raise, so consider guarding for None before comparing or reconfiguring the encoding.

Addressed.

@ikostan
Copy link
Copy Markdown
Owner Author

ikostan commented May 15, 2026

  • The new workspace/test_injection.sh script assumes godot, git, and python3 are on PATH and available; if you intend to use this as a general local helper, you may want to add explicit checks with clear error messages when those binaries are missing.

I just read through the exact test_injection.sh file, and the dependency check is completely missing.

Our script defines the variables at the top:

GODOT_CMD="godot"
RAW_SECRET='T3st_S@lt!_2026#"\'
export PRODUCTION_SALT="$RAW_SECRET"

# Cross-platform sed for in-place editing (macOS vs Linux)
sedi() {
  if [ "$(uname)" = "Darwin" ]; then
    sed -i '' "$@"
  else
    sed -i "$@"
  fi
}

And then immediately jumps into:

echo "=========================================="
echo " Starting Local CI/CD Simulation"
echo "=========================================="

if [ ! -f "scripts/core/globals.gd" ] || [ ! -f "project.godot" ] || [ ! -f "export_presets.cfg" ]; then
    echo "❌ ERROR: Required project files not found!"
    exit 1
fi

It checks if the files exist (globals.gd, project.godot), but it never checks if the software binaries (godot, python3, sed) are actually installed on the system. If a user runs this script without Python installed, it will crash halfway through.

To finally resolve this issue, we need to paste this exact block into test_injection.sh right after the sedi() function and before "Starting Local CI/CD Simulation":

echo "=========================================="
echo " Checking System Dependencies"
echo "=========================================="
for cmd in $GODOT_CMD python3 bash sed; do
    if ! command -v $cmd >/dev/null 2>&1; then
        echo "❌ ERROR: Required command '$cmd' is not installed or not in your PATH."
        exit 1
    fi
done

@ikostan
Copy link
Copy Markdown
Owner Author

ikostan commented May 15, 2026

@sourcery-ai review

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In .github/scripts/inject_salt.sh you build GODOT_ESCAPED/SED_ESCAPED and define sedi, but never actually invoke sed to replace the placeholder in the target file—right now the script logs and exits without modifying the file; add the in‑place replacement step.
  • workspace/test_injection.sh defines the sedi helper function twice with identical implementations; drop the duplicate definition to avoid confusion and future drift.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `.github/scripts/inject_salt.sh` you build `GODOT_ESCAPED`/`SED_ESCAPED` and define `sedi`, but never actually invoke `sed` to replace the placeholder in the target file—right now the script logs and exits without modifying the file; add the in‑place replacement step.
- `workspace/test_injection.sh` defines the `sedi` helper function twice with identical implementations; drop the duplicate definition to avoid confusion and future drift.

## Individual Comments

### Comment 1
<location path="tests/ci/test_ci_flag_injection.py" line_range="164-167" />
<code_context>
+    assert content.count('custom_features="ci"') == 1
+
+
+def test_inject_ci_flag_multiple_presets(repo_tmp):
+    """Verifies that the script updates all available presets in the file."""
+    root = Path(repo_tmp)
+    config = root / "export_presets.cfg"
+
+    multi_preset_content = (
+        "[preset.0]\n" 'name="Web"\n\n' "[preset.1]\n" 'custom_features=""\n'
+    )
+    config.write_text(multi_preset_content, encoding="utf-8")
+
+    result = run_ci_injection(root)
+    assert result.returncode == 0
+
+    content = config.read_text(encoding="utf-8")
+
+    # Both preset sections should contain the CI feature flag and remain intact
+    assert "[preset.0]" in content
+    assert "[preset.1]" in content
+    assert content.count('custom_features="ci"') == 2
+
+
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test case where `custom_features` appears under `[preset.*.options]` to assert it is not touched by the injector.

Given the nuance that only root preset headers should be updated (not `[preset.X.options]` sections), please add a test with `custom_features` inside a `[preset.0.options]` block and assert it remains unchanged, while the root `[preset.0]` entry is still injected/normalized. This will lock in the scoping behavior and guard against future regex changes affecting option-level keys.

```suggestion
    content = config.read_text(encoding="utf-8")
    assert content.count('custom_features="ci"') == 1


def test_inject_ci_flag_does_not_touch_preset_options(repo_tmp):
    """Ensures option-level `custom_features` keys are not modified."""
    root = Path(repo_tmp)
    config = root / "export_presets.cfg"

    preset_with_options = (
        "[preset.0]\n"
        'custom_features=""\n\n'
        "[preset.0.options]\n"
        'custom_features="foo,bar"\n'
    )
    config.write_text(preset_with_options, encoding="utf-8")

    result = run_ci_injection(root)
    assert result.returncode == 0

    updated_content = config.read_text(encoding="utf-8")

    # Root preset gets the CI flag
    assert "[preset.0]" in updated_content
    assert 'custom_features="ci"' in updated_content

    # Options section remains unchanged
    assert "[preset.0.options]" in updated_content
    assert 'custom_features="foo,bar"' in updated_content

    # Ensure we only injected at the root preset level
    assert updated_content.count('custom_features="ci"') == 1


```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread tests/ci/test_ci_flag_injection.py
ikostan and others added 2 commits May 14, 2026 21:36
Modified the test to assert that invalid custom_features keys inside option blocks are actively scrubbed, maintaining Godot configuration integrity.
This commit fixes the style issues introduced in d09b838 according to the output
from Black and isort.

Details: #607
Comment thread tests/ci/conftest.py
@ikostan
Copy link
Copy Markdown
Owner Author

ikostan commented May 15, 2026

  • n .github/scripts/inject_salt.sh you build GODOT_ESCAPED/SED_ESCAPED and define sedi, but never actually invoke sed to replace the placeholder in the target file—right now the script logs and exits without modifying the file; add the in‑place replacement step.

We can completely ignore that feedback—the bot is hallucinating or reviewing an older version of our file!

If we look at the exact inject_salt.sh file we just uploaded, we can see that the final step is absolutely there and perfectly implemented.

At the very bottom of the file (lines 36-37), we have:

# 3. Replace the safe placeholder with the real secret
sedi "s|\"CI_INJECT_SALT_HERE\"|\"$SED_ESCAPED\"|g" "$TARGET_FILE"

We are properly invoking the sedi wrapper we just built, and it is actively modifying the file.

Sometimes these AI code review bots (like Sourcery or DeepSource) cache older commits or lose context when you make rapid changes across multiple files. We have already fully addressed this, and the script is completely functional. We can dismiss that specific comment on GitHub!

@ikostan
Copy link
Copy Markdown
Owner Author

ikostan commented May 15, 2026

  • workspace/test_injection.sh defines the sedi helper function twice with identical implementations; drop the duplicate definition to avoid confusion and future drift.

The bot caught a classic copy-paste glitch!

If we look at the test_injection.sh file we just uploaded, starting right around line 24, we accidentally pasted the exact same sedi() function twice in a row.

While bash will technically just let the second one overwrite the first one and run fine, it is messy and confusing for anyone reading the script.

Furthermore, the system dependency check we talked about in the last step is still missing from this file! Let's clean up both issues in one shot.

The Fix

Replace everything from line 20 (GODOT_CMD="godot") down to the Starting Local CI/CD Simulation echo block with this perfectly clean, single-function version that also includes your dependency guards

The bot caught a classic copy-paste glitch!

If you look at the test_injection.sh file you just uploaded, starting right around line 24, you accidentally pasted the exact same sedi() function twice in a row.

While bash will technically just let the second one overwrite the first one and run fine, it is messy and confusing for anyone reading the script.

Furthermore, the system dependency check we talked about in the last step is still missing from this file! Let's clean up both issues in one shot.

The Fix
Replace everything from line 20 (GODOT_CMD="godot") down to the Starting Local CI/CD Simulation echo block with this perfectly clean, single-function version that also includes your dependency guards
@ikostan
Copy link
Copy Markdown
Owner Author

ikostan commented May 15, 2026

@sourcery-ai review

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • You now have multiple places that manipulate editor plugins and export presets (deploy_to_itch.yml, browser_test.yml, workspace/test_injection.sh, inject_ci_flag.py); consider consolidating this logic (or at least reusing the Python helper consistently) to avoid future drift between CI and local workflows.
  • The sedi helper function for cross‑platform sed appears in both inject_salt.sh and workspace/test_injection.sh; factoring this into a single shared implementation (or at least keeping one canonical version) would make it easier to maintain escaping behavior reliably.
  • In globals.gd you rely on the literal placeholder CI_INJECT_SALT_HERE to decide whether to crash in production; if that string ever changes in one place and not the other you’ll get subtle behavior changes, so it may be safer to centralize the sentinel value (e.g., a const) or assert on Globals.save_encryption_pass.is_empty() instead of a string literal.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- You now have multiple places that manipulate editor plugins and export presets (deploy_to_itch.yml, browser_test.yml, workspace/test_injection.sh, inject_ci_flag.py); consider consolidating this logic (or at least reusing the Python helper consistently) to avoid future drift between CI and local workflows.
- The `sedi` helper function for cross‑platform `sed` appears in both `inject_salt.sh` and `workspace/test_injection.sh`; factoring this into a single shared implementation (or at least keeping one canonical version) would make it easier to maintain escaping behavior reliably.
- In `globals.gd` you rely on the literal placeholder `CI_INJECT_SALT_HERE` to decide whether to crash in production; if that string ever changes in one place and not the other you’ll get subtle behavior changes, so it may be safer to centralize the sentinel value (e.g., a const) or assert on `Globals.save_encryption_pass.is_empty()` instead of a string literal.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@ikostan
Copy link
Copy Markdown
Owner Author

ikostan commented May 15, 2026

  • n globals.gd you rely on the literal placeholder CI_INJECT_SALT_HERE to decide whether to crash in production; if that string ever changes in one place and not the other you’ll get subtle behavior changes, so it may be safer to centralize the sentinel value (e.g., a const) or assert on Globals.save_encryption_pass.is_empty() instead of a string literal.

We must absolutely reject this suggestion because the bot completely failed to understand how your pipeline actually works, and following its advice will literally destroy your game.

🚨 Reject Feedback #3 (globals.gd sentinel)

The bot wants you to abstract "CI_INJECT_SALT_HERE" into a constant. Do not do this. Because your inject_salt.sh script uses a global sed replacement (/g), it blindly replaces that exact string everywhere it finds it. If you abstract it to a constant, here is what your production code will look like after the CI pipeline runs:

const SENTINEL: String = "REAL_PRODUCTION_SECRET"
var salt: String = "REAL_PRODUCTION_SECRET"

# ... later down the file ...

if salt == SENTINEL: 
    OS.crash("CRITICAL SECURITY ERROR") 

Because "REAL_PRODUCTION_SECRET" is perfectly equal to "REAL_PRODUCTION_SECRET", our production game will instantly crash on startup every single time. The bot's suggestion is a trap. We can reply to the comment with: "Rejecting this suggestion. Because the CI pipeline uses global text-replacement, abstracting this to a constant would cause the post-injection validation to constantly evaluate to true, crashing the production build."

@ikostan
Copy link
Copy Markdown
Owner Author

ikostan commented May 15, 2026

  • You now have multiple places that manipulate editor plugins and export presets (deploy_to_itch.yml, browser_test.yml, workspace/test_injection.sh, inject_ci_flag.py); consider consolidating this logic (or at least reusing the Python helper consistently) to avoid future drift between CI and local workflows.
  • The sedi helper function for cross‑platform sed appears in both inject_salt.sh and workspace/test_injection.sh; factoring this into a single shared implementation (or at least keeping one canonical version) would make it easier to maintain escaping behavior reliably.

Zero new bugs is a massive victory! That means the core logic, the Python scripts, and the shell pipelines are all perfectly stable.

At this point, Sourcery is just playing "architect" and nitpicking your project structure.

✅ Resolve Feedbacks #1 & #2 (The Clean Refactor)

The bot is right that sedi is duplicated, and our sed command to disable editor plugins is scattered across YAML and Bash files. We can silence both of these warnings by creating a single, shared utility file.

Step A: Create a shared utils script

Create a new file at .github/scripts/ci_utils.sh and add this exact code:

#!/bin/bash
# .github/scripts/ci_utils.sh

# Cross-platform sed for in-place editing (macOS vs Linux)
sedi() {
  if [ "$(uname)" = "Darwin" ]; then
    sed -i '' "$@"
  else
    sed -i "$@"
  fi
}

# Shared function to safely disable Godot editor plugins before headless exports
disable_editor_plugins() {
    local target_file="${1:-project.godot}"
    echo "🔌 Disabling editor plugins in $target_file to prevent headless crashes..."
    sedi '/^\[editor_plugins\]/,/^\[/ s/^enabled=PackedStringArray.*/enabled=PackedStringArray()/' "$target_file"
}

**Step B: Clean up inject_salt.sh**

Remove the sedi function block from our .github/scripts/inject_salt.sh, and add this source command right after our variable checks:

# Source the shared utilities
source "$(dirname "$0")/ci_utils.sh"

Step C: Clean up `test_injection.sh

Remove the sedi function block AND the raw sed command that disables the plugins from workspace/test_injection.sh. Replace them by sourcing the utility script and calling the new function:

# ... (around line 21, right after your dependency check) ...
source .github/scripts/ci_utils.sh

echo "=========================================="
echo " Starting Local CI/CD Simulation"
echo "=========================================="

# ... (skip down to the plugin section) ...

# Disable editor plugins using the shared function
disable_editor_plugins "project.godot"

Step D: Update your YAML workflows

Finally, anywhere in our GitHub Actions (like browser_test.yml or deploy_to_itch.yml) where we currently have that raw sed command, change the run step to:

      - name: "Disable Editor Plugins (Prevent Headless Crash)"
        run: |
          source .github/scripts/ci_utils.sh
          disable_editor_plugins "project.godot"

By doing this, we've centralized our cross-platform logic and our Godot environment prep into one neat file. If we ever need to change how plugins are disabled, we only have to change it in ci_utils.sh. That should fully satisfy the bot and get this PR ready to merge!

ikostan added 2 commits May 14, 2026 22:10
Introduce .github/scripts/ci_utils.sh with cross-platform sedi and disable_editor_plugins helpers. Update inject_salt.sh to source the shared utilities instead of redefining sedi, and refactor workspace/test_injection.sh to source the utilities, add dependency checks and improved logging, and call disable_editor_plugins to avoid headless Godot crashes. This centralizes CI-related functions and ensures consistent behavior across macOS/Linux environments.
Replace inline sed snippets with sourcing .github/scripts/ci_utils.sh and calling disable_editor_plugins("project.godot") in both browser_test.yml and deploy_to_itch.yml. This centralizes the editor-plugin disabling logic, removes duplicated sed commands, and makes the behavior reusable and easier to maintain across CI workflows.
@ikostan
Copy link
Copy Markdown
Owner Author

ikostan commented May 15, 2026

@sourcery-ai review

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In .github/scripts/inject_salt.sh you currently compute GODOT_ESCAPED/SED_ESCAPED and source ci_utils.sh but never actually run sed/sedi to replace the CI_INJECT_SALT_HERE placeholder, so the script is effectively a no-op; add the in-place substitution step to perform the intended injection.
  • In workspace/test_injection.sh the RAW_SECRET assignment uses a single-quoted string containing an unescaped single-quote/backslash sequence ('T3st_S@lt!_2026#"\'), which will break shell parsing; switch to double quotes or escape the embedded quote properly so the variable is set as intended.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `.github/scripts/inject_salt.sh` you currently compute `GODOT_ESCAPED`/`SED_ESCAPED` and source `ci_utils.sh` but never actually run `sed`/`sedi` to replace the `CI_INJECT_SALT_HERE` placeholder, so the script is effectively a no-op; add the in-place substitution step to perform the intended injection.
- In `workspace/test_injection.sh` the `RAW_SECRET` assignment uses a single-quoted string containing an unescaped single-quote/backslash sequence (`'T3st_S@lt!_2026#"\'`), which will break shell parsing; switch to double quotes or escape the embedded quote properly so the variable is set as intended.

## Individual Comments

### Comment 1
<location path="tests/ci/test_salt_injection.py" line_range="106-113" />
<code_context>
+    ), f"Artifact pollution detected. Unexpected files found: {unexpected}"
+
+
+def test_injection_multiple_placeholders(repo_tmp):
+    """Ensures global replacement updates all occurrences, leaving no partial leftovers."""
+    dummy_rel = f"{repo_tmp}/dummy_multi.gd"
+    dummy_abs = Path(PROJECT_ROOT) / dummy_rel
+
+    dummy_abs.write_text(
+        "extends Node\n"
+        'var security = {"save_salt": "CI_INJECT_SALT_HERE"}\n'
+        'var another = {"save_salt": "CI_INJECT_SALT_HERE"}\n',
+        encoding="utf-8",
+    )
+
+    result = run_injection(dummy_rel, "multi-placeholder-salt")
+
+    assert result.returncode == 0
+    content = dummy_abs.read_text(encoding="utf-8")
+    assert "CI_INJECT_SALT_HERE" not in content
+    assert content.count("multi-placeholder-salt") == 2
+
+
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding a case where the placeholder appears multiple times on the same line

The current test covers multiple occurrences across lines. To better validate the `sed` behavior, please also add a case where `CI_INJECT_SALT_HERE` appears multiple times on a single line (e.g., a long JSON-like literal) to confirm it performs a truly global substitution per line.

```suggestion
    result = run_injection(dummy_rel, "multi-placeholder-salt")

    assert result.returncode == 0
    content = dummy_abs.read_text(encoding="utf-8")
    assert "CI_INJECT_SALT_HERE" not in content
    assert content.count("multi-placeholder-salt") == 2


def test_injection_multiple_placeholders_same_line(repo_tmp):
    """Ensures sed performs a truly global substitution when placeholders repeat on one line."""
    dummy_rel = f"{repo_tmp}/dummy_multi_same_line.gd"
    dummy_abs = Path(PROJECT_ROOT) / dummy_rel

    dummy_abs.write_text(
        "extends Node\n"
        'var security = {"salt1": "CI_INJECT_SALT_HERE", "salt2": "CI_INJECT_SALT_HERE"}\n',
        encoding="utf-8",
    )

    result = run_injection(dummy_rel, "multi-placeholder-salt")

    assert result.returncode == 0
    content = dummy_abs.read_text(encoding="utf-8")
    assert "CI_INJECT_SALT_HERE" not in content
    # Both placeholders on the same line must be replaced
    assert content.count("multi-placeholder-salt") == 2
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +106 to +113
result = run_injection(dummy_rel, "multi-placeholder-salt")

assert result.returncode == 0
content = dummy_abs.read_text(encoding="utf-8")
assert "CI_INJECT_SALT_HERE" not in content
assert content.count("multi-placeholder-salt") == 2


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): Consider adding a case where the placeholder appears multiple times on the same line

The current test covers multiple occurrences across lines. To better validate the sed behavior, please also add a case where CI_INJECT_SALT_HERE appears multiple times on a single line (e.g., a long JSON-like literal) to confirm it performs a truly global substitution per line.

Suggested change
result = run_injection(dummy_rel, "multi-placeholder-salt")
assert result.returncode == 0
content = dummy_abs.read_text(encoding="utf-8")
assert "CI_INJECT_SALT_HERE" not in content
assert content.count("multi-placeholder-salt") == 2
result = run_injection(dummy_rel, "multi-placeholder-salt")
assert result.returncode == 0
content = dummy_abs.read_text(encoding="utf-8")
assert "CI_INJECT_SALT_HERE" not in content
assert content.count("multi-placeholder-salt") == 2
def test_injection_multiple_placeholders_same_line(repo_tmp):
"""Ensures sed performs a truly global substitution when placeholders repeat on one line."""
dummy_rel = f"{repo_tmp}/dummy_multi_same_line.gd"
dummy_abs = Path(PROJECT_ROOT) / dummy_rel
dummy_abs.write_text(
"extends Node\n"
'var security = {"salt1": "CI_INJECT_SALT_HERE", "salt2": "CI_INJECT_SALT_HERE"}\n',
encoding="utf-8",
)
result = run_injection(dummy_rel, "multi-placeholder-salt")
assert result.returncode == 0
content = dummy_abs.read_text(encoding="utf-8")
assert "CI_INJECT_SALT_HERE" not in content
# Both placeholders on the same line must be replaced
assert content.count("multi-placeholder-salt") == 2

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

Labels

bug Something isn't working dependabot dependencies Pull requests that update a dependency file github actions Pull requests that update GitHub Actions code github_actions Pull requests that update GitHub Actions code security setup testing web

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

[BUG] Encryption Key Generation Failure in Web Export [FEATURE] Refactor SALT Injection to Shared Shell Script

1 participant