package slidestest // Who may read or write what, asked of the six holes the GPT-6 (codex) // review of 2026-09-08 found in it. Each test names the finding it // pins, and each one failed before the fix in the same commit. // // They live together rather than beside their subjects because they are // one subject: a capability in this app is supposed to be a thing you // were given, not a string you can name. Every finding here was a place // where naming was enough. import ( "bufio" "bytes" "context" "fmt" "net/http" "net/http/httptest" "net/url" "regexp" "strings" "testing" "time" "amadan.net/rastrillo/rastrillo/db" slides "amadan.net/oficina/slides/internal/slides" ) // --- codex 1: an uploaded asset stops being writable once it is verified --- // TestVerifiedAssetCannotBeOverwritten is the finding itself: a member // asked for a PUT URL naming an address that was already registered and // verified, and the store signed an unconditional PUT for it — so // replacement bytes changed what every surface served, in this deck and // in any other holding the same ref, with no document update anywhere. // // Two halves, because the two stores take different code: the // development PUT comes through this app, and a real bucket's does not. func TestVerifiedAssetCannotBeOverwritten(t *testing.T) { a := newAppWithStore(t, dirStore(t)) cookies := signedIn(t, a, allowedAddress) token := mintAgent(t, a, cookies, "claude") sqid := apiDeck(t, a, token, "Immutable") deck := "/d/" + sqid record := upload(t, a, deck, cookies, "chart.png", "image/png", onePixelPNG, "A chart") registerInDeck(t, a, token, sqid, record) ref := record["ref"].(string) mustGet(t, a, deck+"/assets/"+ref, http.StatusOK, cookies) // Asking for a URL to the verified address does not get one: what // comes back is a staging address of the app's own, and it is not // the ref. signed := postJSON(t, a, deck+"/uploads", fmt.Sprintf(`{"name":"chart.png","size":%d,"type":"image/png","hash":%q}`, len(onePixelPNG), ref), cookies) if signed.Code != http.StatusOK { t.Fatalf("sign: status %d\n%s", signed.Code, signed.Body) } if got, _ := jsonBody(t, signed)["ref"].(string); got == ref { t.Fatalf("signing answered the verified address %s", ref) } // And the PUT this app receives in development refuses an address // no receipt of its own covers, which the verified one never can be. evil := []byte("\x89PNG\r\n\x1a\nnot the chart you registered") if rec := putBytes(t, a, deck+"/uploads/"+ref, "image/png", evil, cookies); rec.Code != http.StatusNotFound { t.Errorf("PUT to a verified address: status %d, want 404\n%s", rec.Code, rec.Body) } // The picture is still the picture. rec := mustGet(t, a, deck+"/assets/"+ref, http.StatusOK, cookies) if !bytes.Equal(rec.Body.Bytes(), onePixelPNG) { t.Errorf("the verified asset changed under it: %d bytes, want the %d that went up", rec.Body.Len(), len(onePixelPNG)) } } // TestStagingAddressIsNeverTheClientsChoice: two signings of the same // file get two different addresses, and neither is the content hash the // browser sent. That is what makes "an address a browser can name is an // address a browser can overwrite" stop being a sentence about this app. func TestStagingAddressIsNeverTheClientsChoice(t *testing.T) { a := newAppWithStore(t, dirStore(t)) cookies := signedIn(t, a, allowedAddress) deck := createDeck(t, a, cookies, "Staging") hash := hashOf(onePixelPNG) seen := map[string]bool{} for i := 0; i < 2; i++ { signed := postJSON(t, a, deck+"/uploads", fmt.Sprintf(`{"name":"x.png","size":%d,"type":"image/png","hash":%q}`, len(onePixelPNG), hash), cookies) if signed.Code != http.StatusOK { t.Fatalf("sign %d: status %d\n%s", i, signed.Code, signed.Body) } key, _ := jsonBody(t, signed)["ref"].(string) if key == hash { t.Fatalf("sign %d answered the address the browser named", i) } if seen[key] { t.Fatalf("two signings answered the same staging address %s", key) } seen[key] = true } } // --- codex 2: knowing a ref is not permission to read it --- // TestRegisteringAnotherDecksRefIsRefused is the finding: registration // checked access to the DESTINATION deck and then fetched whatever ref // it was handed, writing that deck's verification row. So a member who // knew another deck's content address — and a ref is a content address, // which is the whole reason serveAsset checks two things — could // register it into a deck of their own and pass both checks. // // Now registration only ever looks at a staged upload of this // principal's, for this deck. A ref alone reaches nothing. func TestRegisteringAnotherDecksRefIsRefused(t *testing.T) { a := newAppWithStore(t, dirStore(t)) cookies := signedIn(t, a, allowedAddress) token := mintAgent(t, a, cookies, "claude") theirs := apiDeck(t, a, token, "Theirs") mine := apiDeck(t, a, token, "Mine") record := upload(t, a, "/d/"+theirs, cookies, "secret.png", "image/png", onePixelPNG, "A chart") registerInDeck(t, a, token, theirs, record) ref := record["ref"].(string) mustGet(t, a, "/d/"+theirs+"/assets/"+ref, http.StatusOK, cookies) // The other deck's ref, named at the other deck's owner's own // second deck. Not a 500, and not a verification: nothing was // staged here. reg := postJSON(t, a, "/d/"+mine+"/assets", fmt.Sprintf(`{"ref":%q,"name":"secret.png","width":1,"height":1,"alt":"a"}`, ref), cookies) if reg.Code != http.StatusNotFound { t.Fatalf("registering another deck's ref: status %d, want 404\n%s", reg.Code, reg.Body) } // Even with the document made to refer to it, which is an op // nothing checks, the bytes stay out of reach. apiJSON(t, a, http.MethodPost, "/api/v1/decks/"+mine+"/ops", token, fmt.Sprintf(`{"ops":[{"op":"register_asset","id":"borrowed1234","ref":%q,"name":"x.png","width":1,"height":1,"alt":"x"}]}`, ref), http.StatusOK) if rec := get(t, a, "/d/"+mine+"/assets/"+ref, cookies...); rec.Code != http.StatusNotFound { t.Errorf("a borrowed ref was served: status %d, want 404", rec.Code) } share := mintShareLink(t, a, "/d/"+mine, cookies) if rec := get(t, a, "/s/"+share+"/assets/"+ref); rec.Code != http.StatusNotFound { t.Errorf("a borrowed ref was served over a share link: status %d, want 404", rec.Code) } } // TestAStagedUploadBelongsToOneDeck: a receipt is not transferable // either. An upload staged for one deck cannot be registered into // another, however firmly the person holds both. func TestAStagedUploadBelongsToOneDeck(t *testing.T) { a := newAppWithStore(t, dirStore(t)) cookies := signedIn(t, a, allowedAddress) one := createDeck(t, a, cookies, "One") two := createDeck(t, a, cookies, "Two") staged := stageUpload(t, a, one, cookies, "x.png", "image/png", onePixelPNG) reg := postJSON(t, a, two+"/assets", fmt.Sprintf(`{"ref":%q,"name":"x.png","width":1,"height":1,"alt":"a"}`, staged), cookies) if reg.Code != http.StatusNotFound { t.Errorf("registering deck one's staged upload into deck two: status %d, want 404\n%s", reg.Code, reg.Body) } // And it is single use: registered once in its own deck, the same // receipt is spent. first := postJSON(t, a, one+"/assets", fmt.Sprintf(`{"ref":%q,"name":"x.png","width":1,"height":1,"alt":"a"}`, staged), cookies) if first.Code != http.StatusOK { t.Fatalf("registering in its own deck: status %d, want 200\n%s", first.Code, first.Body) } again := postJSON(t, a, one+"/assets", fmt.Sprintf(`{"ref":%q,"name":"x.png","width":1,"height":1,"alt":"a"}`, staged), cookies) if again.Code != http.StatusNotFound { t.Errorf("registering a spent receipt: status %d, want 404\n%s", again.Code, again.Body) } } // --- codex 5: following a presentation stays behind the share link --- // TestFollowStaysBehindTheShareLink is the finding: following handed // the audience the presentation session sqid, and every later read went // to /p/{session}, which asks only for a live presentation. So revoking // the share link left the recipient reading fresh deck content until // the talk ended — a capability the person who granted it could not // take back. func TestFollowStaysBehindTheShareLink(t *testing.T) { a := newApp(t) cookies := signedIn(t, a, allowedAddress) path := sampleDeck(t, a, cookies) link := mintShareLink(t, a, path, cookies) share := "/s/" + link session, remote := startPresenting(t, a, path, cookies) command(t, a, session, "next", remote, "", nil) follow := mustGet(t, a, share+"?follow=1", http.StatusOK, nil).Body.String() if strings.Contains(follow, session) { t.Errorf("the follow surface carries the session sqid %s:\n%s", session, follow) } if strings.Contains(follow, "/p/") { t.Errorf("the follow surface carries a /p/ URL:\n%s", follow) } // The two URLs its script uses, both the share link's own. if !strings.Contains(follow, `data-stream="`+share+`/stream"`) { t.Errorf("the follow surface does not stream from the share link:\n%s", follow) } if !strings.Contains(follow, `data-fragment="`+share+`?follow=1&slide={n}&fragment=1"`) { t.Errorf("the follow surface does not fetch fragments from the share link:\n%s", follow) } // And they work: the fragment is a bare slide, the stream speaks. fragment := mustGet(t, a, share+"?follow=1&slide=3&fragment=1", http.StatusOK, nil).Body.String() if !strings.HasPrefix(strings.TrimSpace(fragment), "