DEV-000048 r2: honor advisory message size contract #21
@@ -18,8 +18,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
maxRequestBytes = 8 << 10
|
||||
maxResponseBytes = 1 << 20
|
||||
maxRequestBytes = 8 << 10
|
||||
maxAdvisoryRequestBytes = 20 << 10
|
||||
maxResponseBytes = 1 << 20
|
||||
)
|
||||
|
||||
//go:embed static
|
||||
@@ -113,7 +114,7 @@ type browserAdvisoryRequest struct {
|
||||
|
||||
func (g *gateway) handleAdvisory(w http.ResponseWriter, r *http.Request) {
|
||||
var browserRequest browserAdvisoryRequest
|
||||
if err := decodeStrictJSON(r.Body, maxRequestBytes, &browserRequest); err != nil || browserRequest.Message == "" || len(browserRequest.Message) > 16*1024 || !validID(browserRequest.SessionID) || !validID(browserRequest.WorkspaceID) {
|
||||
if err := decodeStrictJSON(r.Body, maxAdvisoryRequestBytes, &browserRequest); err != nil || browserRequest.Message == "" || len(browserRequest.Message) > 16*1024 || !validID(browserRequest.SessionID) || !validID(browserRequest.WorkspaceID) {
|
||||
http.Error(w, "invalid advisory request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/cognitive"
|
||||
)
|
||||
|
||||
func TestGatewayForwardsOnlyCreatorAllowlist(t *testing.T) {
|
||||
@@ -251,6 +253,7 @@ func TestAdvisoryRequiresSessionBeforeUpstreamCalls(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdvisoryAssemblesTrustedIdentityAndScope(t *testing.T) {
|
||||
longMessage := strings.Repeat("a", 16*1024)
|
||||
core := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("Authorization") != "Bearer bounded-session" {
|
||||
t.Fatal("missing bounded session")
|
||||
@@ -272,8 +275,12 @@ func TestAdvisoryAssemblesTrustedIdentityAndScope(t *testing.T) {
|
||||
cognitive := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
text := string(body)
|
||||
if !strings.Contains(text, `"ActorID":"creator-actor"`) || !strings.Contains(text, `"PrincipalID":"creator-principal"`) || !strings.Contains(text, `"ScopeID":"trusted-scope"`) || !strings.Contains(text, `"Message":"help"`) || strings.Contains(text, "attacker") {
|
||||
t.Fatalf("untrusted cognitive request: %s", text)
|
||||
var trusted cognitive.AdvisoryRequest
|
||||
if err := json.Unmarshal(body, &trusted); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if trusted.ActorID != "creator-actor" || trusted.PrincipalID != "creator-principal" || trusted.ScopeID != "trusted-scope" || trusted.Message != longMessage || strings.Contains(text, "attacker") {
|
||||
t.Fatal("cognitive request was not assembled from trusted context")
|
||||
}
|
||||
var request map[string]any
|
||||
_ = json.Unmarshal(body, &request)
|
||||
@@ -284,7 +291,7 @@ func TestAdvisoryAssemblesTrustedIdentityAndScope(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
request := httptest.NewRequest(http.MethodPost, "/api/advisory", strings.NewReader(`{"message":"help","session_id":"focus-session","workspace_id":"home"}`))
|
||||
request := httptest.NewRequest(http.MethodPost, "/api/advisory", strings.NewReader(fmt.Sprintf(`{"message":%q,"session_id":"focus-session","workspace_id":"home"}`, longMessage)))
|
||||
request.Header.Set("Authorization", "Bearer bounded-session")
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"task_file": "dev/tasks/DEV-000048-creator-advisory-console.json",
|
||||
"task_id": "DEV-000048",
|
||||
"task_revision": 1
|
||||
"task_revision": 2
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"task_id": "DEV-000048",
|
||||
"task_revision": 1,
|
||||
"task_revision": 2,
|
||||
"architecture_revision": "ADR-0006",
|
||||
"base_repo_revision": "861438a4cae844edfe8a17615fb700d17c950d8e",
|
||||
"risk_tier": "E",
|
||||
@@ -17,7 +17,7 @@
|
||||
"persistence_and_migrations": [],
|
||||
"failure_retry_restart": ["advisory text is not persisted", "session/focus failure prevents cognitive call", "provider unavailable is displayed explicitly", "no automatic retry"],
|
||||
"assumptions": ["browser supplies only message and existing focus locators", "deterministic fake remains the only binding", "docker-test remains prohibited"],
|
||||
"acceptance_scenarios": ["unauthenticated advisory denied before cognitive call", "browser cannot select Actor/Principal/Scope/binding/provider/context", "trusted request uses current session and validated focus", "UI labels advisory as non-canonical and unavailable", "message is not stored in local/session storage", "desktop/mobile layout and console are clean"],
|
||||
"acceptance_scenarios": ["unauthenticated advisory denied before cognitive call", "browser cannot select Actor/Principal/Scope/binding/provider/context", "trusted request uses current session and validated focus", "the gateway accepts the full 16 KiB advisory message contract plus its JSON envelope", "UI labels advisory as non-canonical and unavailable", "message is not stored in local/session storage", "desktop/mobile layout and console are clean"],
|
||||
"required_tests": ["unit", "gateway adversarial", "static UI", "go test", "go vet", "architecture preflight", "exact diff scope", "browser desktop/mobile", "CI"],
|
||||
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user