Skip to content

Harden G1.x rewriter pipeline: 5 correctness/security fixes

Placeholder ppxd requested to merge feat/calamari-g1-hardening into main

Summary

Followups to the G1.1–G1.3 architectural review. Stacks on #365 (G1.3) — base is the G1.3 branch so the JsonPathReplacer changes here build on the file landed in that PR. After #365 merges, this PR can be retargeted at master (or just merged through after a rebase).

Zero wire-literal changes — SquidWeb unaffected. Purely internal correctness + security hardening.

What's fixed

# Concern What broke Fix
#1 (closed) JSON namespace pollution A variable named Squid.Deployment.Id would clobber an operator's literal JSON leaf at the same path — silent corruption with no operator-facing cause JsonPathReplacer.TryFindVariable skips dot-form lookups starting with Squid.. Colon form (Squid:X:Y) stays as the explicit-intent escape hatch
#2 G1.3 non-atomic write A 50MB appsettings.json write killed mid-way left the operator with a corrupted config New shared EncodingPreservingFileIO.WriteAllTextAtomic (sibling temp + rename), reused by G1.1 + G1.2 + G1.3
#3 G1.3 BOM loss VS-generated appsettings.json with UTF-8 BOM lost the BOM on every rewrite — polluted deploy diffs Same shared helper exposes ReadAllTextPreservingEncoding (extracted from G1.1's local copy); G1.3 + G1.1 both use it
#4 JSON encoder over-escape Default System.Text.Json encoder turns <, >, &, + into \uXXXX — operator's URL/HTML strings unreadable in diffs WriteOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping. Safe because target is IConfiguration, never injected into HTML
#5 (closed) Symlink-escape attack Malicious package can plant evil.config -> /etc/passwd; on Linux Tentacle running as root, the rewriter overwrites host files GlobMatcher adds match-level layer: resolve canonical real path via FileInfo.ResolveLinkTarget; drop matches whose real path escapes rootDir. Fail-closed on resolution errors

What's in the box

Layer File
Shared file-IO primitive src/Squid.Calamari/Commands/Common/EncodingPreservingFileIO.cs (new)
JSON namespace + encoder src/Squid.Calamari/Commands/StructuredConfig/JsonPathReplacer.cs
G1.3 atomic + BOM src/Squid.Calamari/Commands/StructuredConfig/StructuredConfigVariablesStep.cs
G1.1 migration to shared src/Squid.Calamari/Commands/Substitution/SubstituteInFilesStep.cs
G1.2 migration to shared src/Squid.Calamari/Commands/Configuration/XdtTransformer.cs
Symlink sandbox src/Squid.Calamari/Commands/Substitution/GlobMatcher.cs
Tests — shared helper (8) tests/Squid.Calamari.Tests/Calamari/Commands/Common/EncodingPreservingFileIOTests.cs (new)
Tests — JSON guard + encoder (5) tests/Squid.Calamari.Tests/Calamari/Commands/StructuredConfig/JsonPathReplacerTests.cs
Tests — symlink (2) tests/Squid.Calamari.Tests/Calamari/Commands/Substitution/GlobMatcherTests.cs

Test plan

  • Squid.Calamari.Tests: 246/246 (was 230; +16 new for the 5 fixes — 13 directly + 3 round-trip integrations)
  • Squid.UnitTests cross-project wire-contract: 5615/5615 — no regression on the 12 G1.1/G1.2/G1.3 drift detectors
  • dotnet build solution-wide: 0 errors
  • Symlink-escape test guarded behind OperatingSystem.IsWindows() skip — symlink creation needs elevation on Windows pre-1809; cross-OS dev hosts stay green
  • Staging IIS deploy of an ASP.NET Core app with appsettings.Production.json containing Squid.* literals (drift-detection scenario from #1 (closed)) + URL connection strings (encoder scenario from #4)

Non-breaking guarantee

Surface Status
Wire literals (Squid.Action.IISWebSite.*) Unchanged
Operator-facing variable names Unchanged
Step toggles + behavior on the happy path Unchanged
Step output / log format Unchanged
Frontend (SquidWeb) wire-literal references None touched — confirmed via grep against SquidWeb/src/pages/project-detail/step-editors/iis/DeployToIisEditor.tsx

The behavior changes (Squid.* skip, symlink reject) are corrections — they reject inputs that produced silent corruption or security bypass before. No correct deploy ever depended on those broken paths.

Merge request reports

Loading