diff --git a/src/spec/checkPackageSpec.ts b/src/spec/checkPackageSpec.ts index 8d10696c..e4faeb96 100644 --- a/src/spec/checkPackageSpec.ts +++ b/src/spec/checkPackageSpec.ts @@ -194,7 +194,14 @@ export function checkPackageSpec({ const composePath = path.join(dir, composeFileName); if (fs.existsSync(composePath)) { const text = fs.readFileSync(composePath, "utf8"); - const interpolated = [...new Set(text.match(/\$\{[^}]*\}/g) || [])]; + // Compose consumes dollar signs in escaped pairs before interpolating. + const interpolated = [ + ...new Set( + [...text.matchAll(/(\$+)\{[^}]*\}/g)] + .filter(match => match[1].length % 2 === 1) + .map(match => match[0].slice(match[1].length - 1)) + ) + ]; if (interpolated.length > 0) add( "SPEC-C04", diff --git a/test/spec/checkPackageSpec.test.ts b/test/spec/checkPackageSpec.test.ts index de08fe77..02f65953 100644 --- a/test/spec/checkPackageSpec.test.ts +++ b/test/spec/checkPackageSpec.test.ts @@ -151,6 +151,57 @@ describe("package spec checks", function () { ).to.include('version "v1.0.0"'); }); + it("accepts escaped compose variables without reporting interpolation", () => { + write("dappnode_package.json", { + name: "demo.dnp.dappnode.eth", + version: "0.1.0" + }); + write( + "docker-compose.yml", + yaml.dump({ + services: { + app: { + image: "alpine:3.21", + environment: { + HEALTH_PORT: "$${PORT:-9090}", + HEALTH_TOKEN: "$${ANON_KEY}", + LITERAL: "$$$${LITERAL}" + } + } + } + }) + ); + expect(check(dir)).to.deep.equal([]); + }); + + it("reports real interpolation after escaped dollar pairs", () => { + write("dappnode_package.json", { + name: "demo.dnp.dappnode.eth", + version: "0.1.0" + }); + write( + "docker-compose.yml", + yaml.dump({ + services: { + app: { + image: "alpine:3.21", + environment: { + PORT: "${PORT:-80}", + PREFIXED: "$$${TOKEN}", + REPEATED: "${PORT:-80}", + ESCAPED: "$${SAFE}" + } + } + } + }) + ); + const findings = check(dir).filter(finding => finding.rule === "SPEC-C04"); + expect(findings).to.have.length(1); + expect(findings[0].message).to.equal( + "Uses environment interpolation ${PORT:-80}, ${TOKEN}; write the values out." + ); + }); + it("accepts digest-pinned args, shorter variant upstream lists and real avatars", () => { write("dappnode_package.json", { upstream: [