From f24ff86b9cb33393ead63dce890f9c1a7548a9fb Mon Sep 17 00:00:00 2001 From: SEHYEON HEO <130676635+jungjipdo@users.noreply.github.com> Date: Fri, 10 Jul 2026 01:29:12 +0900 Subject: [PATCH 1/2] fix(export): include renamed PoC base contract in output export allowlist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The engine renamed the PoC base contract file from LumosPoCBase.sol to Base.sol: the generated PoC.t.sol now does `import "./Base.sol"` / `contract AttackTest is Base`, and the engine writes Base.sol on disk (deleting the legacy LumosPoCBase.sol). The output export artifact allowlist was never updated for that rename and still enumerated only LumosPoCBase.sol. Effect on every exported bundle: the allowlisted LumosPoCBase.sol is reported as missing (engine no longer writes it) while the on-disk Base.sol is never exported (not allowlisted). The base contract the PoC imports is silently dropped, so an exported PoC project fails to compile standalone: Source "test/Base.sol" not found. Add Base.sol next to each LumosPoCBase.sol entry in both the default and export artifact profiles, at both PoC locations (report_bundle/poc/ and the agent_poc foundry test dir). Purely additive: legacy LumosPoCBase.sol entries are kept for backward compatibility, absent files are still reported as missing rather than erroring, and no orchestration/state/DB logic changes — this only widens the read-only export allowlist. Co-Authored-By: Claude Fable 5 --- internal/artifacts/ecw_reader_test.go | 31 +++++++++++++++++++++++++++ internal/artifacts/reader.go | 3 +++ 2 files changed, 34 insertions(+) diff --git a/internal/artifacts/ecw_reader_test.go b/internal/artifacts/ecw_reader_test.go index dd7b4ff..28361c2 100644 --- a/internal/artifacts/ecw_reader_test.go +++ b/internal/artifacts/ecw_reader_test.go @@ -69,6 +69,37 @@ func TestECWReaderExposesDedicatedProfileWithoutChangingDefault(t *testing.T) { } } +// TestECWReaderServesRenamedPoCBaseContract guards the self-containment of the +// exported PoC project. The engine renamed the PoC base contract file from +// LumosPoCBase.sol to Base.sol (PoC.t.sol imports "./Base.sol"); the export +// allowlist must expose Base.sol at both PoC locations so the exported bundle +// compiles standalone instead of dropping the base file the PoC imports. +func TestECWReaderServesRenamedPoCBaseContract(t *testing.T) { + base := t.TempDir() + root := filepath.Join(base, "case_1") + writeArtifactFile(t, root, "report_bundle/poc/PoC.t.sol", "import \"./Base.sol\";\ncontract AttackTest is Base {}\n") + writeArtifactFile(t, root, "report_bundle/poc/Base.sol", "abstract contract Base {}\n") + writeArtifactFile(t, root, "artifacts/agent_poc/foundry/test/PoC.t.sol", "import \"./Base.sol\";\ncontract AttackTest is Base {}\n") + writeArtifactFile(t, root, "artifacts/agent_poc/foundry/test/Base.sol", "abstract contract Base {}\n") + + reader, err := NewECWReader(base, 0) + if err != nil { + t.Fatal(err) + } + for _, rel := range []string{"report_bundle/poc/Base.sol", "artifacts/agent_poc/foundry/test/Base.sol"} { + if !slices.Contains(reader.AllowedPaths(), rel) { + t.Fatalf("ECW export profile missing renamed PoC base %q; exported PoC would not build", rel) + } + result, err := reader.Read(caseWithRoot(root), rel, 0) + if err != nil { + t.Fatalf("ECW Read(%q): %v", rel, err) + } + if result.Text != "abstract contract Base {}\n" { + t.Fatalf("unexpected PoC base content for %q: %+v", rel, result) + } + } +} + func writeArtifactFile(t *testing.T, root, rel, body string) { t.Helper() path := filepath.Join(root, rel) diff --git a/internal/artifacts/reader.go b/internal/artifacts/reader.go index 2258ac0..6cf6370 100644 --- a/internal/artifacts/reader.go +++ b/internal/artifacts/reader.go @@ -33,6 +33,7 @@ var defaultArtifacts = []artifactSpec{ {PublicPath: "report_bundle/report/report.json", SourcePath: "report_bundle/report/report.json"}, {PublicPath: "report_bundle/report/run_summary.json", SourcePath: "report_bundle/report/run_summary.json"}, {PublicPath: "report_bundle/poc/PoC.t.sol", SourcePath: "report_bundle/poc/PoC.t.sol"}, + {PublicPath: "report_bundle/poc/Base.sol", SourcePath: "report_bundle/poc/Base.sol"}, {PublicPath: "report_bundle/poc/LumosPoCBase.sol", SourcePath: "report_bundle/poc/LumosPoCBase.sol"}, {PublicPath: "report_bundle/evidence/asset_deltas.json", SourcePath: "report_bundle/evidence/asset_deltas.json"}, {PublicPath: "report_bundle/evidence/fund_flows.json", SourcePath: "report_bundle/evidence/fund_flows.json"}, @@ -62,6 +63,7 @@ var ecwArtifacts = []artifactSpec{ {PublicPath: "report_bundle/report/report.json", SourcePath: "report_bundle/report/report.json"}, {PublicPath: "report_bundle/report/run_summary.json", SourcePath: "report_bundle/report/run_summary.json"}, {PublicPath: "report_bundle/poc/PoC.t.sol", SourcePath: "report_bundle/poc/PoC.t.sol"}, + {PublicPath: "report_bundle/poc/Base.sol", SourcePath: "report_bundle/poc/Base.sol"}, {PublicPath: "report_bundle/poc/LumosPoCBase.sol", SourcePath: "report_bundle/poc/LumosPoCBase.sol"}, {PublicPath: "report_bundle/evidence/asset_deltas.json", SourcePath: "report_bundle/evidence/asset_deltas.json"}, {PublicPath: "report_bundle/evidence/fund_flows.json", SourcePath: "report_bundle/evidence/fund_flows.json"}, @@ -82,6 +84,7 @@ var ecwArtifacts = []artifactSpec{ {PublicPath: "artifacts/agent_poc/summary.json", SourcePath: "artifacts/agent_poc/summary.json"}, {PublicPath: "artifacts/agent_poc/foundry/foundry.toml", SourcePath: "artifacts/agent_poc/foundry/foundry.toml"}, {PublicPath: "artifacts/agent_poc/foundry/test/PoC.t.sol", SourcePath: "artifacts/agent_poc/foundry/test/PoC.t.sol"}, + {PublicPath: "artifacts/agent_poc/foundry/test/Base.sol", SourcePath: "artifacts/agent_poc/foundry/test/Base.sol"}, {PublicPath: "artifacts/agent_poc/foundry/test/LumosPoCBase.sol", SourcePath: "artifacts/agent_poc/foundry/test/LumosPoCBase.sol"}, {PublicPath: "artifacts/agent_poc/foundry/lib/forge-std/src/Test.sol", SourcePath: "artifacts/agent_poc/foundry/lib/forge-std/src/Test.sol"}, {PublicPath: "artifacts/poc_sketch/poc_sketch.sol", SourcePath: "artifacts/poc_sketch/poc_sketch.sol"}, From c634e7d3c8f840b693951db3721a72b88fb697be Mon Sep 17 00:00:00 2001 From: SEHYEON HEO <130676635+jungjipdo@users.noreply.github.com> Date: Fri, 10 Jul 2026 16:48:50 +0900 Subject: [PATCH 2/2] test(export): cover current PoC base artifact --- deploy/README.md | 9 ++++++--- docs/results.md | 3 ++- internal/artifacts/reader_test.go | 10 ++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/deploy/README.md b/deploy/README.md index 2ac6a45..d3c1ba0 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -265,7 +265,8 @@ The only artifact paths exposed are the text-oriented report bundle files: - `report_bundle/report/report.json` - `report_bundle/report/run_summary.json` - `report_bundle/poc/PoC.t.sol` -- `report_bundle/poc/LumosPoCBase.sol` +- `report_bundle/poc/Base.sol` (current PoC support contract) +- `report_bundle/poc/LumosPoCBase.sol` (legacy compatibility) - `report_bundle/evidence/asset_deltas.json` - `report_bundle/evidence/fund_flows.json` - `report_bundle/visuals/asset_deltas.dot` @@ -310,8 +311,10 @@ intentionally mount the output directory read-only to the MCP runtime. ## ECW internal export Backlight can expose a dedicated ECW bundle without changing the operator API, -MCP tool list, worker queue, or the product artifact allowlist. The endpoint is -disabled unless a separate token is configured: +MCP tool list, or worker queue. The ECW endpoint uses a broader allowlist than +the narrow product artifact profile, which includes the current and legacy PoC +support contracts required by report bundles. The endpoint is disabled unless +a separate token is configured: ```bash HELIOS_ECW_EXPORT_TOKEN= diff --git a/docs/results.md b/docs/results.md index 85b0ca3..9912e3b 100644 --- a/docs/results.md +++ b/docs/results.md @@ -128,7 +128,8 @@ Common artifact paths: - `report_bundle/report/report.json` - `report_bundle/report/run_summary.json` - `report_bundle/poc/PoC.t.sol` -- `report_bundle/poc/LumosPoCBase.sol` +- `report_bundle/poc/Base.sol` (current PoC support contract) +- `report_bundle/poc/LumosPoCBase.sol` (legacy compatibility) - `report_bundle/evidence/asset_deltas.json` - `report_bundle/evidence/fund_flows.json` - `report_bundle/visuals/asset_deltas.dot` diff --git a/internal/artifacts/reader_test.go b/internal/artifacts/reader_test.go index e361a5c..9ee477d 100644 --- a/internal/artifacts/reader_test.go +++ b/internal/artifacts/reader_test.go @@ -40,6 +40,9 @@ func TestReaderExposesPrettyProductArtifactAliases(t *testing.T) { if err := os.WriteFile(filepath.Join(root, "report_bundle", "poc", "LumosPoCBase.sol"), []byte("abstract contract LumosPoCBase {}\n"), 0o644); err != nil { t.Fatal(err) } + if err := os.WriteFile(filepath.Join(root, "report_bundle", "poc", "Base.sol"), []byte("abstract contract Base {}\n"), 0o644); err != nil { + t.Fatal(err) + } if err := os.WriteFile(filepath.Join(root, "report_bundle", "evidence", "asset_deltas.json"), []byte(`[]`), 0o644); err != nil { t.Fatal(err) } @@ -151,6 +154,13 @@ func TestReaderExposesPrettyProductArtifactAliases(t *testing.T) { if pocBase.Text != "abstract contract LumosPoCBase {}\n" { t.Fatalf("unexpected PoC base: %+v", pocBase) } + currentPoCBase, err := reader.Read(c, "report_bundle/poc/Base.sol", 0) + if err != nil { + t.Fatal(err) + } + if currentPoCBase.Text != "abstract contract Base {}\n" { + t.Fatalf("unexpected current PoC base: %+v", currentPoCBase) + } for _, path := range []string{"summary.json", "summary.md", "rca.md", "Report.md", "report_bundle/visuals/asset_deltas.png", "artifacts/secret.json", "../RCA.md", "/tmp/RCA.md"} { if _, err := reader.Read(c, path, 0); err == nil {