diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md
index 297ca4c..ecf87b8 100644
--- a/.paul/ROADMAP.md
+++ b/.paul/ROADMAP.md
@@ -6,6 +6,31 @@ Console tool for CNC program transfer and file management. Fanuc FOCAS base, exp
## Current Milestone
+**v0.7 Build Hardening** (v0.7.0)
+Status: π§ In Progress
+Phases: 0 of 2 complete
+
+Theme: Formalizzare pipeline build sicura β obfuscation Release (ConfuserEx), license bypass Debug, fix review findings v0.6 (ISS-012..017).
+
+| Phase | Name | Plans | Status | Completed |
+|-------|------|-------|--------|-----------|
+| 17 | build-pipeline | TBD | Not started | - |
+| 18 | review-fixes | TBD | Not started | - |
+
+### Phase 17: build-pipeline
+
+Focus: ConfuserEx Release obfuscation pipeline, Debug license bypass (`#if !DEBUG`), `tools/` gitignore, docs build process.
+Plans: TBD (defined during /paul:plan)
+
+### Phase 18: review-fixes
+
+Focus: Chiudere review findings v0.6 β ISS-012 (Fagor ReadActiveProgram), ISS-013 (FanucProgram coupling), ISS-014 (ResolvePath NRE), ISS-015 (file encoding UTF-8 BOM), ISS-016 (validator path fallback), ISS-017 (Ping swallow log).
+Plans: TBD (defined during /paul:plan)
+
+---
+
+## Past Milestones
+
**v0.1 Multi-CN Release** (v0.1.0)
Status: β
Complete
Phases: 5 of 5 complete
@@ -227,4 +252,4 @@ Completed: 2026-05-18
- [x] 16-02: Fagor fixup post-review (validator extension fix, Γ± escape, lock tests Siemens/Heidenhain, factory test)
---
-*Roadmap updated: 2026-05-25 β v0.6 milestone complete; Phase 16 (fagor-ftp) 16-01 + 16-02 complete.*
+*Roadmap updated: 2026-05-25 β v0.7 Build Hardening milestone created; Phases 17 (build-pipeline) + 18 (review-fixes) scaffolded.*
diff --git a/.paul/STATE.md b/.paul/STATE.md
index ef201e1..ae48b28 100644
--- a/.paul/STATE.md
+++ b/.paul/STATE.md
@@ -5,15 +5,15 @@
See: .paul/PROJECT.md (updated 2026-05-18)
**Core value:** Machinists transfer CNC programs to/from any controller without manual steps or internet
-**Current focus:** v0.6 Fagor Support complete; ready for v0.7 planning
+**Current focus:** v0.7 Build Hardening β formalize obfuscation pipeline + close v0.6 review findings
## Current Position
-Milestone: v0.6 Fagor Support β **Complete**
-Phase: 16 of 16 (fagor-ftp) β Complete
-Plan: 16-02 complete
-Status: Milestone complete β ready for v0.7 planning
-Last activity: 2026-05-25 β Phase 16-02 fixup complete (post-review); v0.6 milestone complete
+Milestone: v0.7 Build Hardening β **In Progress**
+Phase: 17 of 18 (build-pipeline) β Not started
+Plan: Not started
+Status: Ready to plan Phase 17
+Last activity: 2026-05-25 β Milestone v0.7 created (phases 17 build-pipeline, 18 review-fixes)
Progress:
- Milestone v0.1: [ββββββββββ] 100% (complete)
@@ -22,14 +22,15 @@ Progress:
- Milestone v0.4: [ββββββββββ] 100% (complete)
- Milestone v0.5: [ββββββββββ] 100% (complete)
- Milestone v0.6: [ββββββββββ] 100% (complete)
-- Phase 15: [ββββββββββ] 100%
-- Phase 16: [ββββββββββ] 100%
+- Milestone v0.7: [ββββββββββ] 0%
+- Phase 17: [ββββββββββ] 0%
+- Phase 18: [ββββββββββ] 0%
## Loop Position
```
PLAN βββΆ APPLY βββΆ UNIFY
- β β β [Loop complete β v0.6 milestone complete]
+ β β β [Ready for Phase 17 PLAN]
```
## What Was Built (v0.6 Fagor Support β complete)
@@ -214,10 +215,17 @@ Key open items:
None β v0.5 complete.
### Git State
-Last commit: `60ce1c9` β feat(15-logging): NLog structured logging β all output through NLog (v0.5 complete)
+Last commit: `68c5a55` β chore(paul): close v0.6 Fagor Support milestone, log ISS-012..017
LicenseGenerator repo: commit `2ef68dd` β feat: initial LicenseGenerator
Branch: main
+Uncommitted (pre-Phase 17 work, to fold into 17-01):
+- Program.cs β `#if !DEBUG` wrap license check
+- NcProgramManager.csproj β Obfuscate target (Release-only)
+- NcProgramManager.crproj β ConfuserEx config
+- tools/ConfuserEx/ β CLI 1.6.0 (vendored; consider gitignore)
+- 4 test files β `result.Error` β `string.Join("; ", result.Errors)` fix
+
## Codebase Mapped
Date: 2026-05-18
@@ -225,9 +233,9 @@ Documents: `.paul/codebase/` (7 files β STACK, ARCHITECTURE, STRUCTURE, CONVEN
## Session Continuity
-Last session: 2026-05-18
-Stopped at: v0.5 milestone complete β Phase 15 both plans done
-Next action: /paul:milestone for v0.6
+Last session: 2026-05-25
+Stopped at: Milestone v0.7 Build Hardening created (phases 17 + 18 scaffolded)
+Next action: /paul:plan for Phase 17 (build-pipeline)
Resume file: .paul/ROADMAP.md
---
diff --git a/.paul/paul.json b/.paul/paul.json
index 3be91ee..3b6a00c 100644
--- a/.paul/paul.json
+++ b/.paul/paul.json
@@ -1,15 +1,15 @@
{
"name": "NcProgramManager",
- "version": "0.6.0",
+ "version": "0.7.0",
"milestone": {
- "name": "v0.6 Fagor Support",
- "version": "0.6.0",
- "status": "complete"
+ "name": "v0.7 Build Hardening",
+ "version": "0.7.0",
+ "status": "in_progress"
},
"phase": {
- "number": 16,
- "name": "fagor-ftp",
- "status": "complete"
+ "number": 17,
+ "name": "build-pipeline",
+ "status": "not_started"
},
"loop": {
"plan": null,
@@ -17,7 +17,7 @@
},
"timestamps": {
"created_at": "2026-05-13T00:00:00Z",
- "updated_at": "2026-05-25T00:00:00Z"
+ "updated_at": "2026-05-25T21:40:00Z"
},
"satellite": {
"groom": true
diff --git a/BUG_ANALYSIS.md b/BUG_ANALYSIS.md
new file mode 100644
index 0000000..198e82a
--- /dev/null
+++ b/BUG_ANALYSIS.md
@@ -0,0 +1,120 @@
+# Analisi Bug & Problemi - nc_program_manager
+
+Data: 2026-05-25
+Scope: progetto `nc_program_manager` (esclusi test, obj/, Properties/)
+
+## π΄ Critici (security)
+
+### FTP plaintext - credenziali esposte (no TLS)
+- `Cnc/Fagor/FagorFtpClient.cs:60` - `ftp://` URI con NetworkCredential, nessun supporto SSL/TLS
+- `Cnc/Mitsubishi/MitsubishiFtpClient.cs:60` - stesso pattern
+- `Cnc/Siemens/SiemensFtpClient.cs:60` - stesso pattern
+
+**Fix**: usare `FtpWebRequest.EnableSsl = true` o migrare a `ftps://` / SFTP. Se rete trusted, documentare requirement esplicito.
+
+### LSV2 credenziali cleartext su TCP raw
+- `Cnc/Heidenhain/Lsv2Client.cs:105` - username+password concatenati con null terminator, inviati non cifrati su socket TCP.
+
+**Fix**: documentare come limitazione protocollo LSV2 oppure incapsulare in TLS tunnel.
+
+### Path traversal - no validation prima FTP delete
+- `Cnc/Fagor/FagorMachine.cs:121` - `DeleteProgramAsync` passa path utente direttamente a `_ftp.DeleteFile()`
+- `Cnc/Mitsubishi/MitsubishiMachine.cs:117` - stesso pattern
+- `Cnc/Siemens/SiemensMachine.cs:117` - stesso pattern
+
+**Fix**: validare path (no `..`, no path absoluti fuori dir consentita) prima di chiamata FTP.
+
+### License bypass in DEBUG build
+- `Program.cs:47` - check licenza saltato in DEBUG conditional compile.
+
+**Fix**: rimuovere `#if DEBUG` skip, o se necessario per dev limitare a `[Conditional]` log non funzionale.
+
+### License read exception swallowed
+- `Licensing/LicenseFile.cs:20` - errori lettura file licenza silenziosamente catturati, bypassano errori.
+
+**Fix**: log + rethrow o ritorno result con error info.
+
+---
+
+## π Bug funzionali
+
+### Program.Replace() return ignorato
+- `FanucProgram.cs:176` - `Program.Replace(...)` chiamato senza assegnazione. String immutable, nessun effetto.
+
+**Fix**: `Program = Program.Replace(...)`.
+
+### Off-by-one in IsoProgramFormatter (commenti ISO)
+- `Cnc/Fanuc/IsoProgramFormatter.cs:19` - `if (closePar > 0)` deve essere `>= 0` per gestire commento a inizio riga
+- `Cnc/Fanuc/IsoProgramFormatter.cs:41` - `if (openPar > 0 && closePar > openPar)` deve essere `openPar >= 0`
+
+**Fix**: cambiare `> 0` in `>= 0` quando si controlla risultato `IndexOf`.
+
+### FanucDialectDetector - comparison string inconsistente
+- `Cnc/Fanuc/FanucDialectDetector.cs:71` - `if (trimmed == "0" || t == " 0")` usa `t` non trimmato a destra mentre `trimmed` a sinistra. Edge case mancato.
+
+**Fix**: uniformare uso di `trimmed` o documentare intent.
+
+---
+
+## π‘ NullReferenceException
+
+### ArgParser - Substring su null
+- `ArgParser.cs:23` - `arguments.Find(...)` ritorna `null` se argomento `-azione=` mancante, poi `.Substring(8)` solleva NRE.
+
+**Fix**: null check prima di Substring, o usare `?.Substring(8)` con default.
+
+### FanucProgram - field Program nullable usato senza check
+- `FanucProgram.cs:116` - `Standardize()` chiama `Program.IndexOf` / `Program.Replace` senza guardia null
+- `FanucProgram.cs:188` - `ToString()` concatena `Program`, `ToolOffsetData`, `WorkZeroOffsetData` potenzialmente null
+
+**Fix**: null check all'ingresso metodi o inizializzazione a `string.Empty` nel costruttore.
+
+---
+
+## π‘ Resource leak / robustness
+
+### FanucMachine - buffer access fragile
+- `Cnc/Fanuc/FanucMachine.cs:407` - `buffer[len-1]` su `num` ritornato da FOCAS user-controlled. Se `len == UploadBufferSize + 1` raggiunge boundary.
+
+**Fix**: clamp `len` su `Math.Min(len, buffer.Length)`.
+
+### Lsv2Client - TcpClient/Stream non disposti se Login fallisce
+- `Cnc/Heidenhain/Lsv2Client.cs:43` - `_stream` assegnato, poi `Login()` puΓ² throware; manca try/finally per cleanup.
+
+**Fix**: wrap connect+login in try/catch con dispose esplicito su exception.
+
+### Lsv2Client - ReadFrame ritorna dati parziali su EOF
+- `Cnc/Heidenhain/Lsv2Client.cs:163` - loop esce su `n == 0` (EOF/disconnect) ma processa buffer incompleto, ritornando frame parziale.
+
+**Fix**: throw o ritorno error se `n == 0` prima di lunghezza attesa.
+
+---
+
+## π’ Nit
+
+### DateTime.Now per timestamp
+- `Cnc/Models/CncError.cs:17` - `DateTime.Now` soggetto a drift/DST. Usare `DateTime.UtcNow` o `DateTimeOffset.UtcNow`.
+
+---
+
+## Riepilogo
+
+| SeveritΓ | Count |
+|----------|-------|
+| Critici security | 8 |
+| Bug funzionali | 4 |
+| NRE | 3 |
+| Resource/robustness | 3 |
+| Nit | 1 |
+| **Totale** | **19** |
+
+## PrioritΓ fix consigliata
+
+1. TLS su FTP + LSV2 (o documenta requirement rete trusted)
+2. Path validation prima delete FTP
+3. Rimuovi DEBUG bypass licenza
+4. Null checks su `FanucProgram.Program` e `ArgParser`
+5. Fix off-by-one `IsoProgramFormatter`
+6. Fix `FanucProgram.Replace()` assignment
+7. Dispose pattern `Lsv2Client`
+8. Logging exception `LicenseFile`
diff --git a/NcProgramManager.Tests/Integration/FagorMachineIntegrationTests.cs b/NcProgramManager.Tests/Integration/FagorMachineIntegrationTests.cs
index 9b15d73..6a07334 100644
--- a/NcProgramManager.Tests/Integration/FagorMachineIntegrationTests.cs
+++ b/NcProgramManager.Tests/Integration/FagorMachineIntegrationTests.cs
@@ -120,7 +120,7 @@ namespace NcProgramManager.Tests.Integration
using (var machine = new FagorMachine(config))
{
var result = machine.ConnectAsync().GetAwaiter().GetResult();
- Assert.That(result.Success, Is.True, "ConnectAsync failed: " + result.Error);
+ Assert.That(result.Success, Is.True, "ConnectAsync failed: " + string.Join("; ", result.Errors));
}
}
}
diff --git a/NcProgramManager.Tests/Integration/HeidenhainMachineIntegrationTests.cs b/NcProgramManager.Tests/Integration/HeidenhainMachineIntegrationTests.cs
index 50f2457..035225f 100644
--- a/NcProgramManager.Tests/Integration/HeidenhainMachineIntegrationTests.cs
+++ b/NcProgramManager.Tests/Integration/HeidenhainMachineIntegrationTests.cs
@@ -94,7 +94,7 @@ namespace NcProgramManager.Tests.Integration
using (var machine = new HeidenhainMachine(config))
{
var result = machine.ConnectAsync().GetAwaiter().GetResult();
- Assert.That(result.Success, Is.True, "ConnectAsync failed: " + result.Error);
+ Assert.That(result.Success, Is.True, "ConnectAsync failed: " + string.Join("; ", result.Errors));
}
}
}
diff --git a/NcProgramManager.Tests/Integration/MitsubishiMachineIntegrationTests.cs b/NcProgramManager.Tests/Integration/MitsubishiMachineIntegrationTests.cs
index 78a4aa0..54fc035 100644
--- a/NcProgramManager.Tests/Integration/MitsubishiMachineIntegrationTests.cs
+++ b/NcProgramManager.Tests/Integration/MitsubishiMachineIntegrationTests.cs
@@ -111,7 +111,7 @@ namespace NcProgramManager.Tests.Integration
using (var machine = new MitsubishiMachine(config))
{
var result = machine.ConnectAsync().GetAwaiter().GetResult();
- Assert.That(result.Success, Is.True, "ConnectAsync failed: " + result.Error);
+ Assert.That(result.Success, Is.True, "ConnectAsync failed: " + string.Join("; ", result.Errors));
}
}
}
diff --git a/NcProgramManager.Tests/Integration/SiemensMachineIntegrationTests.cs b/NcProgramManager.Tests/Integration/SiemensMachineIntegrationTests.cs
index 918e85b..a07d0c4 100644
--- a/NcProgramManager.Tests/Integration/SiemensMachineIntegrationTests.cs
+++ b/NcProgramManager.Tests/Integration/SiemensMachineIntegrationTests.cs
@@ -119,7 +119,7 @@ namespace NcProgramManager.Tests.Integration
using (var machine = new SiemensMachine(config))
{
var result = machine.ConnectAsync().GetAwaiter().GetResult();
- Assert.That(result.Success, Is.True, "ConnectAsync failed: " + result.Error);
+ Assert.That(result.Success, Is.True, "ConnectAsync failed: " + string.Join("; ", result.Errors));
}
}
}
diff --git a/NcProgramManager.csproj b/NcProgramManager.csproj
index fb608d8..de1b25a 100755
--- a/NcProgramManager.csproj
+++ b/NcProgramManager.csproj
@@ -163,4 +163,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/Program.cs b/Program.cs
index 9e246c7..774e1a3 100755
--- a/Program.cs
+++ b/Program.cs
@@ -35,6 +35,7 @@ namespace NcProgramManager
return 0;
}
+#if !DEBUG
string licFingerprint = new MachineFingerprint().GetFingerprint();
string licBase64;
string licPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "license.lic");
@@ -45,6 +46,9 @@ namespace NcProgramManager
VediErrore(-100, inputArgs.debugMode);
return -1;
}
+#else
+ _log.Info("DEBUG build - licenza non richiesta");
+#endif
bool needsIp = inputArgs.manufacturer != CncManufacturer.Fanuc || inputArgs.nodoHssb == -1;
if (inputArgs.azione == "" || (needsIp && string.IsNullOrEmpty(inputArgs.ip)) || inputArgs.pathLocaleProgramma == "")