Bugfix 1.5.7
release-tag / release-image (push) Successful in 6m16s

This commit is contained in:
2026-08-27 12:50:24 +02:00
parent f7f3460f6c
commit 5899989228
11 changed files with 332 additions and 18 deletions
@@ -525,6 +525,43 @@ func TestCriticalIdentifierGuardRejectsInventedVersion(t *testing.T) {
}
}
func TestCriticalIdentifierGuardIgnoresSlashCompoundsAndURLPaths(t *testing.T) {
draft := stagingDraftPayload{
Title: "BitLocker Wiederherstellung",
Text: "Nach einer TPM-, BIOS-/UEFI- oder Hardwareänderung kann die Wiederherstellung erforderlich sein. Prüfen Sie die Web-/Portal-Konfiguration und dokumentieren Sie Interaktionsbereiche/-Tags.",
Answer: "Öffnen Sie die Herstellerdokumentation unter https://example.test/docs/portal-konfiguration/uefi-recovery.",
}
evidence := []draftEvidence{{Memory: core.Memory{Text: "BitLocker recovery can be triggered after TPM, BIOS, UEFI, or hardware changes."}, Source: &core.KnowledgeSource{Title: "Microsoft", URI: "https://learn.microsoft.com/windows/security/operating-system-security/data-protection/bitlocker/recovery-overview"}}}
if err := validateDraftCriticalIdentifiers(draft, evidence); err != nil {
t.Fatalf("slash compounds and URL paths must not be treated as CLI identifiers: %v", err)
}
}
func TestCriticalIdentifierGuardRejectsInventedSlashSwitchInCode(t *testing.T) {
draft := stagingDraftPayload{
Title: "DISM Reparatur",
Text: "Verwenden Sie nur dokumentierte Reparaturoptionen.",
Answer: "Führen Sie `DISM /Online /Cleanup-Image /MagicRepair` aus.",
}
evidence := []draftEvidence{{Memory: core.Memory{Text: "Run DISM /Online /Cleanup-Image /RestoreHealth to repair the image."}, Source: &core.KnowledgeSource{Title: "Microsoft", URI: "https://learn.microsoft.com/windows-hardware/manufacture/desktop/repair-a-windows-image"}}}
err := validateDraftCriticalIdentifiers(draft, evidence)
if err == nil || !strings.Contains(strings.ToLower(err.Error()), "/magicrepair") {
t.Fatalf("invented slash switch in code must be rejected, got %v", err)
}
}
func TestCriticalIdentifierGuardAcceptsSourcedSlashSwitchInCode(t *testing.T) {
draft := stagingDraftPayload{
Title: "DISM Reparatur",
Text: "Verwenden Sie nur dokumentierte Reparaturoptionen.",
Answer: "Führen Sie `DISM /Online /Cleanup-Image /RestoreHealth` aus.",
}
evidence := []draftEvidence{{Memory: core.Memory{Text: "Run DISM /Online /Cleanup-Image /RestoreHealth to repair the image."}, Source: &core.KnowledgeSource{Title: "Microsoft", URI: "https://learn.microsoft.com/windows-hardware/manufacture/desktop/repair-a-windows-image"}}}
if err := validateDraftCriticalIdentifiers(draft, evidence); err != nil {
t.Fatalf("sourced slash switches in code must pass: %v", err)
}
}
func TestClaimVerificationRepairsUnsupportedDISMOrder(t *testing.T) {
chatCalls := 0
s, e := policyTestEngine(t, func(w http.ResponseWriter, r *http.Request) {
@@ -10,6 +10,7 @@ import (
"sort"
"strconv"
"strings"
"unicode"
"neuroforge/internal/core"
)
@@ -298,18 +299,59 @@ func evidencePackForPrompt(cfg StagingPublisherConfig, evidence []draftEvidence)
return b.String()
}
var criticalIdentifierRE = regexp.MustCompile(`(?i)\b(?:0x[0-9a-f]{4,}|cve-\d{4}-\d{4,}|kb\d{5,}|v?\d+\.\d+(?:\.\d+){0,2})\b|-\d{3,}|/[A-Za-z][A-Za-z0-9-]{2,}`)
var (
criticalIdentifierRE = regexp.MustCompile(`(?i)\b(?:0x[0-9a-f]{4,}|cve-\d{4}-\d{4,}|kb\d{5,}|v?\d+\.\d+(?:\.\d+){0,2})\b|-\d{3,}`)
slashSwitchRE = regexp.MustCompile(`/[A-Za-z][A-Za-z0-9-]{2,}`)
markdownCodeRE = regexp.MustCompile("(?s)```(?:[A-Za-z0-9_+.-]+)?[\t ]*\n?(.*?)```|`([^`\n]+)`")
)
// slashSwitchIdentifiers intentionally considers slash-prefixed identifiers only
// inside Markdown code spans/fences. A bare `/word` in prose is highly ambiguous:
// German compounds such as "BIOS-/UEFI-Konfiguration" and URL paths such as
// "/portal-konfiguration" previously tripped the source-grounding gate even though
// they were not CLI switches. Actionable commands are already claim-verified, so the
// deterministic identifier guard should be conservative and only add the extra
// slash-token check when the draft itself marks content as code.
func slashSwitchIdentifiers(text string) []string {
var out []string
for _, match := range markdownCodeRE.FindAllStringSubmatch(text, -1) {
segment := ""
if len(match) > 1 && match[1] != "" {
segment = match[1]
} else if len(match) > 2 {
segment = match[2]
}
for _, loc := range slashSwitchRE.FindAllStringIndex(segment, -1) {
if loc[0] > 0 {
prev := segment[loc[0]-1]
// URL paths (host/path), compound prose (BIOS-/UEFI) and
// filesystem-like fragments are not command switches.
if !unicode.IsSpace(rune(prev)) && prev != '(' && prev != '[' && prev != '{' && prev != '"' && prev != '\'' {
continue
}
}
out = append(out, segment[loc[0]:loc[1]])
}
}
return out
}
func criticalIdentifiers(parts ...string) []string {
seen := map[string]bool{}
var out []string
add := func(m string) {
k := strings.ToLower(strings.TrimSpace(m))
if k != "" && !seen[k] {
seen[k] = true
out = append(out, k)
}
}
for _, p := range parts {
for _, m := range criticalIdentifierRE.FindAllString(p, -1) {
k := strings.ToLower(strings.TrimSpace(m))
if k != "" && !seen[k] {
seen[k] = true
out = append(out, k)
}
add(m)
}
for _, m := range slashSwitchIdentifiers(p) {
add(m)
}
}
return out