Guard source previews against content version substitution
Why: Opening a processed source must not show changed current bytes as the quoted version. Evidence: SELF TaskSourceContent regression fails before and passes after expected-hash enforcement; artifact regressions and live compilation pass. Impact: Optional content hash narrows the existing authorized artifact read; controlled fixtures select a genuinely provided quotation. Refs: fast-slow-u2-u3 source-version acceptance
This commit is contained in:
@@ -1382,6 +1382,13 @@ func (s *Server) artifactContent(w http.ResponseWriter, r *http.Request) {
|
||||
writeStoreErr(w, err)
|
||||
return
|
||||
}
|
||||
if expected := r.URL.Query().Get("expectedHash"); expected != "" {
|
||||
sum := sha256.Sum256(content)
|
||||
if hex.EncodeToString(sum[:]) != expected {
|
||||
http.Error(w, "来源已更新,请从文件列表查看当前版本;任务中的引用片段仍可用于历史核验", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
}
|
||||
ct := contentTypeOf(a)
|
||||
w.Header().Set("Content-Type", ct)
|
||||
setArtifactHeaders(w, ct)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"agent-system-backend/internal/store"
|
||||
core "agent-system-core"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTaskSourceContentDoesNotSubstituteLatestVersion(t *testing.T) {
|
||||
srv, st := newTestServer(t)
|
||||
original := []byte("上线六月")
|
||||
a := core.Artifact{Owner: store.OwnerDefault, ProjectID: "p", Name: "input.txt"}
|
||||
if e := st.CreateArtifact(&a, original); e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
sum := sha256.Sum256(original)
|
||||
hash := hex.EncodeToString(sum[:])
|
||||
get := func() *httptest.ResponseRecorder {
|
||||
r := httptest.NewRequest("GET", "/api/artifacts/"+a.ID+"/content?expectedHash="+hash, nil)
|
||||
r.SetPathValue("id", a.ID)
|
||||
w := httptest.NewRecorder()
|
||||
srv.artifactContent(w, r)
|
||||
return w
|
||||
}
|
||||
if w := get(); w.Code != http.StatusOK || w.Body.String() != string(original) {
|
||||
t.Fatal("original version not readable", w.Code, w.Body.String())
|
||||
}
|
||||
if e := st.UpdateArtifactContent(store.OwnerDefault, a.ID, []byte("上线七月")); e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
if w := get(); w.Code != http.StatusConflict {
|
||||
t.Fatal("latest content masquerades as processed version", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,8 @@ func evalMechanismStream(ctx context.Context, model string, msgs []agentllm.Mess
|
||||
count++
|
||||
}
|
||||
}
|
||||
matches := regexp.MustCompile(`sourceId=(src_[a-f0-9]+) blockIndex=(\d+)`).FindAllStringSubmatch(sourceText, -1)
|
||||
matches := regexp.MustCompile(`sourceId=(src_[a-f0-9]+) blockIndex=(\d+)[^\n]*\n引用定位片段:([^\n]*)`).FindAllStringSubmatch(sourceText, -1)
|
||||
matches = filterCheckpointSources(matches)
|
||||
if count < 2 && len(matches) > 0 {
|
||||
var block int
|
||||
fmt.Sscan(matches[0][2], &block)
|
||||
@@ -318,3 +319,13 @@ func evalProviderStream(route map[string]any, dir string) agentllm.StreamFunc {
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
func filterCheckpointSources(matches [][]string) [][]string {
|
||||
var out [][]string
|
||||
for _, match := range matches {
|
||||
if len(match) > 3 && strings.Contains(match[3], "上线六月") {
|
||||
out = append(out, match)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user