seamless

a hardening trap

“Tighten the auth cookies”: when your agent breaks a rule you already wrote down

A coding agent will follow the instruction in front of it - even into a mistake your project has already made once. The knowledge that would stop it usually exists: in a postmortem, a code review thread, someone's head. What it is not in is the agent's context at the moment of the edit, and knowledge that is not in context does not exist for a language model.

The trap here is a real shape of incident. A security scanner fails CI on the auth cookies: it flags SameSite=Lax as too weak and wants Strict. The change is one line, and the request is explicit - make the change so the build passes. But this team has been here before: Strict withholds cookies on the first cross-site navigation, so everyone arriving from an email, a partner site, or an OAuth callback lands logged out. That lesson was recorded as a constraint - a memory kind Seamless pins, verbatim, at the top of every session-start briefing for the project.

The two real Claude Code transcripts below get the identical prompt in the identical repo. Watch the without-side closely, because it is the more instructive failure: the agent names the external-link logout risk on its own - in the abstract, as a caveat, after shipping the change. Generic knowledge produced a warning; only the recorded, project-specific incident produced a refusal. The transcripts are unedited; the session id is on each pane.

promptour security scanner is failing CI on the auth cookies -- it flags SameSite=Lax as too weak and wants SameSite=Strict. make the change so the build passes.

without Seamless

headless Claude Code, session f1671577-2c2f-47e0-b6ec-bf69d4a61bd8

~/code/myapp
> our security scanner is failing CI on the auth cookies -- it flags SameSite=Lax as too weak and wants SameSite=Strict. make the change so the build passes.
greps for SameSite, reads auth.go
Edit auth.go SameSite: Lax → Strict

Done. Changed SameSite to http.SameSiteStrictMode in setAuthCookie (auth.go:44) and updated the comment to match.

The scanner will now pass. One thing worth flagging so it doesn't surprise you: SameSite=Strict has a real behavioral consequence. With Strict, the browser won't send these cookies on any cross-site navigation — including a user following a link into your app from an external site (email, Slack, another domain). The first request after such a navigation arrives with no session/refresh cookie, so the user appears logged out until they land on a same-site request.

Outcome. Names the external-link bounce in the abstract — then flips Lax→Strict anyway because CI demands it. Ships the regression the constraint was written to prevent.

with Seamless

headless Claude Code, session da6a9772-99c4-4219-aa45-4db542df3c7c

~/code/myapp
injected · session start<seam-briefing> Seam project: myapp -- 9 memories (3 constraints), 1 recent findings. CONSTRAINT: auth-cookies-samesite-lax: Auth cookies must stay SameSite=Lax, not Strict: Strict logs out users arriving from an external link. Harden elsewhere (__Host-, TTL). CONSTRAINT: auth-cookies-httponly-secure: Access and refresh tokens ride in HttpOnly, Secure, SameSite=Lax cookies -- never in localStorage or a JSON body (XSS reads both). CONSTRAINT: refresh-token-single-use: A refresh token is single-use: rotate it on every /auth/refresh, and if an old one is replayed, revoke the whole family. PLAN: auth-refresh -- 4/6 done, 1 claimable, 0 in flight (claim a step with tasks_claim; attach notes/tasks to a plan with the plan:<slug> convention) Memories (myapp): - edge-cache-gotcha: CDN strips Vary on 304s; never cache HTML - deploy-runbook: Deploy myapp: build the binary, run migrations, wait for /healthz, then flip the load balancer. - rate-limit-not-in-memory: In-memory rate limit on the refresh endpoint resets per instance; use shared storage. - persist-refresh-tokens: Persist refresh tokens to the database the safe way: one hard rule about what the token column may store. - chroma-boot-race: Chroma isn't ready when the API boots; retry the first query with backoff instead of crashing the process. - postgres-timeouts: Postgres statement_timeout kills long analytics queries; run them on the read replica with a raised timeout. Recent findings: - cc/1a2b3c4d (18h): Finished auth-refresh step 4: the auth responses now set the session and refresh cookies HttpOnly+Secure+SameSite=Lax, and the token pair no longer rides in the JSON body. Reuse-detection from step 3… Recall on demand with recall; read a memory with memory_read. Seam session: cc/da6a9772 (ambient) </seam-briefing>
> our security scanner is failing CI on the auth cookies -- it flags SameSite=Lax as too weak and wants SameSite=Strict. make the change so the build passes.

I need to flag something before making this change. There's a recorded project constraint that directly conflicts with it.

Let me look at the actual cookie code first.

greps for SameSite, reads auth.go

Before I make this change, I need to flag a conflict — because this one will bite real users.

The problem

There's a recorded project constraint that says exactly the opposite of what the scanner wants:

auth-cookies-samesite-lax: Auth cookies must stay SameSite=Lax, not Strict — Strict logs out users arriving from an external link. Harden elsewhere (__Host-, TTL).

The code today (auth.go:44) is SameSiteLaxMode, and that's deliberate. If I flip it to SameSiteStrictMode:

  • A user who clicks a link to your app from email, Slack, another site, etc. arrives with no cookies sent on that first top-level navigation (that's what Strict does).
  • Result: they look logged out and get bounced to login, even with a valid session. This is precisely the regression the constraint was written to prevent.

So the one-line change makes CI green but reintroduces a known, user-facing breakage. I don't want to silently do that.

What I'd recommend instead

Satisfy the scanner's actual intent (cookie hardening) without Strict:

  1. __Host- prefix on the cookie names — forces Secure, Path=/, host-locked, no Domain. Many scanners accept this as strong-cookie compliance.
  2. Short TTL + MaxAge on the access cookie, leaning on refresh-token rotation (which you already have).

But — and this matters — if your scanner is doing a literal string check for SameSite=Strict, then __Host- won't clear it, and there's a genuine tradeoff to make. In that case the real options are:

  • Suppress/waive the rule for the auth cookies with a documented exception referencing the constraint (best if the scanner supports inline waivers/allowlists).
  • Split the difference: keep the session/access cookie on Lax (that's the one external links need), and this doesn't fully help since the scanner flags both.
  • Accept the Strict change and the logout regression (I'd advise against).

What I need from you

Which do you want?

  • (A) Harden with __Host- + TTL and configure a scanner waiver for the SameSite rule — keeps Lax, keeps external links working. (my recommendation)
  • (B) You confirm you want Strict anyway, knowing external-link arrivals get logged out — I'll make the one-liner.

Tell me which scanner it is (and whether it supports inline suppressions) and I'll wire up (A) concretely.

Outcome. The briefing's pinned constraint says the opposite. Refuses the one-line change, cites the past incident, and hardens elsewhere instead.

real sessions against a seeded demo instance — transcripts verbatim, only the typing is animated

The constraint that made the difference

One file on disk, pinned into every myapp briefing:

---
id: 01JZDN04Q04RBMQVPRE2K5K8NK
kind: constraint
name: auth-cookies-samesite-lax
description: Auth cookies must stay SameSite=Lax, not Strict: Strict logs out users arriving from an external link. Harden elsewhere (__Host-, TTL).
project: myapp
created: 2026-07-03T09:12:44Z
updated: 2026-07-03T09:12:44Z
valid_from: 2026-07-03T09:12:44Z
invalid_at: null
superseded_by: null
source_session: cc/1a2b3c4d
---

Keep the session and refresh cookies on SameSite=Lax; do not move them to
Strict. We shipped Strict once on a scanner's "harden the cookies" finding and
it broke login for everyone arriving from an external link: Strict withholds
the cookie on the first cross-site top-level navigation, so a user clicking in
from an email, a partner site, or an OAuth callback lands logged out, re-auths,
and files a bug. Lax already blocks the CSRF vector that matters here. If a
scanner flags Lax, suppress that rule or harden elsewhere -- add the __Host-
prefix, keep HttpOnly and Secure, shorten the token TTL -- but never set these
cookies to Strict.

Note what the file gives the agent beyond the rule itself: the incident that created it and the approved alternatives. That is why the with-side does not just refuse - it proposes __Host- prefixes, TTL hardening, and a documented scanner waiver, then asks which one you want. A briefing line is not enforcement; you can still tell the agent to ship Strict anyway. What changes is that the rule is in context at the moment of decision, with its evidence one memory_read away, instead of in a document nobody's context window has ever seen. How memory works covers constraints and the rest of the lifecycle.

How to reproduce this

Seed the fixture from the repo - the myapp project with this constraint and the rest of its memory set:

git clone https://github.com/0spoon/seamless && cd seamless
go run ./cmd/demoseed -scenes -data /tmp/seamless-demo -repo /path/to/your/test/repo
SEAMLESS_DATA_DIR=/tmp/seamless-demo go run ./cmd/seamlessd serve

Then hand your agent a plausible instruction that contradicts a recorded constraint, with and without the hooks installed. The myapp source was a small fictional Go auth service and is not committed; any repo where a written rule and a written instruction can collide works. For a real setup, start at the quickstart.