Skip to content

Commit bd6ac65

Browse files
committed
review: drip-182 batch 3 — aaif-goose/goose#8916+8904
- aaif-goose/goose#8916 fix(bedrock): cache trailing message for stable prefix across agent turns (merge-as-is) - aaif-goose/goose#8904 fix(oidc-proxy): validate exp independently of MAX_TOKEN_AGE_SECONDS (merge-as-is — security fix with test inversion in same commit)
1 parent 036b077 commit bd6ac65

2 files changed

Lines changed: 133 additions & 0 deletions

File tree

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
pr: block/goose#8904
3+
sha: 2eb4a5d9966f72ea23c67a24f780110c4c5a01f4
4+
verdict: merge-as-is
5+
reviewed_at: 2026-04-29T18:31:00Z
6+
---
7+
8+
# fix(oidc-proxy): validate exp independently of MAX_TOKEN_AGE_SECONDS (#8832)
9+
10+
## Context
11+
12+
In `oidc-proxy/src/index.js` `verifyOidcToken`, the previous code
13+
had:
14+
15+
```js
16+
if (env.MAX_TOKEN_AGE_SECONDS) {
17+
const age = ...;
18+
if (age > parseInt(env.MAX_TOKEN_AGE_SECONDS, 10)) {
19+
return { valid: false, reason: "Token too old" };
20+
}
21+
} else if (!payload.exp || payload.exp < Date.now() / 1000) {
22+
return { valid: false, reason: "Token expired" };
23+
}
24+
```
25+
26+
The `else if` is the bug: when `MAX_TOKEN_AGE_SECONDS` is set, the
27+
`exp` check is *unreachable*. A signed token with `exp` 5 minutes
28+
in the past but `iat` within the configured window is accepted as
29+
valid. The proxy still validates signature and issuer earlier in
30+
the flow, so this isn't an "unsigned token wins" bug, but it is a
31+
clean spec violation: RFC 7519 says `exp` is a hard upper bound on
32+
acceptable token lifetime.
33+
34+
## What's good
35+
36+
- Fix is the minimal one-liner: replace `else if` with `if` so both
37+
checks run independently. Author resisted the temptation to
38+
refactor the surrounding validation pipeline, which would have
39+
blurred the diff.
40+
- The test update at `oidc-proxy/test/index.test.js:232` is the
41+
most valuable part of the PR. The previous test was literally
42+
asserting the buggy behavior:
43+
`it("accepts recently-expired token within MAX_TOKEN_AGE_SECONDS", ...) → expect(response.status).toBe(200)`.
44+
The new test inverts both the name and the assertion:
45+
`it("rejects expired token even when within MAX_TOKEN_AGE_SECONDS", ...) → expect(response.status).toBe(401); expect((await response.json()).error).toBe("Token expired")`.
46+
That's a textbook example of "the test was documenting the bug" —
47+
having the new test land in the same commit forecloses regression.
48+
- Author calls out that the test file change was *intentional* in
49+
the PR body ("Updated the existing test that inadvertently
50+
documented the bypass behavior"). That kind of explicit reasoning
51+
in a security-adjacent PR is the right operator hygiene — saves
52+
the next reviewer from wondering why a test went from passing to
53+
inverted.
54+
- DCO sign-off mentioned in checklist; matches contributor
55+
conventions for `block/goose`.
56+
57+
## Concerns / nits
58+
59+
- None substantive. A nit: the `reason: "Token expired"` string is
60+
reused from the old branch. Consumers parsing reasons will not
61+
see a behavior change, which is good.
62+
63+
## Verdict
64+
65+
`merge-as-is` — security fix, smallest possible diff, test is
66+
inverted in the same commit so the regression can't quietly come
67+
back. This should land on the next patch release.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
pr: block/goose#8916
3+
sha: 00c2141debc4eff86146ed4450ba2249a20ceec2
4+
verdict: merge-as-is
5+
reviewed_at: 2026-04-29T18:31:00Z
6+
---
7+
8+
# fix(bedrock): cache trailing message for stable prefix across agent turns
9+
10+
## Context
11+
12+
In `crates/goose/src/providers/bedrock.rs` (`BedrockProvider::converse`,
13+
around line 232), the previous code placed prompt-cache breakpoints on
14+
the first three visible messages
15+
(`const MESSAGE_CACHE_BUDGET: usize = 3; let cache_count = … visible_messages.len().min(MESSAGE_CACHE_BUDGET)`)
16+
and then iterated `enumerate()` setting cache=true for `idx < cache_count`.
17+
This PR replaces that with a single trailing-message cache point.
18+
19+
The author's reasoning matches Anthropic's prompt caching contract:
20+
cache reads walk *backward* from the breakpoint, hashing the prefix.
21+
A cache point pinned to position 0..3 means everything appended after
22+
position 3 has to be reprocessed every turn — linear growth in turn
23+
count. A trailing breakpoint means the next turn's lookback (≤20
24+
blocks) finds the previous turn's write, and only the new content
25+
between turns gets fresh processing.
26+
27+
## What's good
28+
29+
- The diff is exactly the change described in the comment — no
30+
drive-by refactors. The new `last_idx = visible_messages.len().checked_sub(1)`
31+
+ `cache_last && Some(idx) == last_idx` pattern is the cleanest
32+
Rust expression of "set the flag for exactly the last element,
33+
or none if the list is empty."
34+
- The misleading old comment ("caching recent messages would shift
35+
positions each turn, causing misses") is replaced with an accurate
36+
description of the lookup model and a documentation link. Future
37+
maintainers won't re-introduce the original mistake based on that
38+
comment.
39+
- The author correctly identified that the existing test surface
40+
(`providers::formats::bedrock` per-message helpers and
41+
`providers::bedrock::test_caching_*` enable-flag tests) doesn't
42+
actually exercise the *placement* of the cache point — it exercises
43+
whether `to_bedrock_message_with_caching` honors the boolean.
44+
Adding a placement test would be valuable but is not strictly
45+
required for correctness; the change itself is mechanically obvious.
46+
- The system-prompt cache point is left untouched, which is correct
47+
— system prompts are stable across turns, so a head-anchored
48+
breakpoint is the right policy there.
49+
50+
## Concerns / nits
51+
52+
- For agent loops that add >20 blocks per turn (large multi-step
53+
tool batches), the trailing-breakpoint strategy degrades to
54+
full-prefix reprocessing because the lookback window is exceeded.
55+
This is documented behavior, not a bug, but worth a comment in
56+
the code or a follow-up that emits a debug log when this
57+
threshold is approached.
58+
- `enable_caching && last_idx.is_some()` could be folded into a
59+
`let cache_last = enable_caching && !visible_messages.is_empty();`
60+
for one less `Option` ceremony. Style nit only.
61+
62+
## Verdict
63+
64+
`merge-as-is` — the analysis is correct, the diff is minimal, the
65+
old comment was wrong and is now right. The 20-block-window
66+
caveat is a follow-up consideration.

0 commit comments

Comments
 (0)