Skip to content

Registry tokens cannot be invalidated: add per-identity iat cutoff (token rotation blocker) - #2263

Closed
jaylfc wants to merge 1 commit into
devfrom
exec/tsk-nslyhc
Closed

Registry tokens cannot be invalidated: add per-identity iat cutoff (token rotation blocker)#2263
jaylfc wants to merge 1 commit into
devfrom
exec/tsk-nslyhc

Conversation

@jaylfc

@jaylfc jaylfc commented Aug 3, 2026

Copy link
Copy Markdown
Owner

CARD TITLE (intent, not commit subject): Registry tokens cannot be invalidated: add per-identity iat cutoff (token rotation blocker)

Autonomous build of board card tsk-nslyhc.

REVIEW WARNING (automated): this card's text asks for tests, but the diff changes no test file. Either the acceptance criteria are unmet or the card needs correcting. Do not merge without resolving this.

Files:
test_token_rotation.py | 203 +++++++++++++++++++++++++++++++++++
tinyagentos/agent_registry_store.py | 61 ++++++++++-
tinyagentos/agent_token_auth.py | 6 ++
tinyagentos/routes/agent_registry.py | 57 ++++++++++
4 files changed, 323 insertions(+), 4 deletions(-)

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jaylfc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 4 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 98291c73-5c8c-45d7-b7b8-4639fad61912

📥 Commits

Reviewing files that changed from the base of the PR and between 00f888d and 8eacc8c.

📒 Files selected for processing (4)
  • test_token_rotation.py
  • tinyagentos/agent_registry_store.py
  • tinyagentos/agent_token_auth.py
  • tinyagentos/routes/agent_registry.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jaylfc

jaylfc commented Aug 3, 2026

Copy link
Copy Markdown
Owner Author

CLOSED - two reasons, neither about the code quality. (1) COLLISION: per-identity iat-cutoff rotation is exactly what open PR #2208 implements (in flight since July, paired with the #2235 token-renewal plan); two independent implementations of the same mechanism means whichever merges first silently decides the design - #2208 keeps the slot. (2) SEQUENCING (standing constraint): rotation must land together with #2235's renewal work or rotation is defeated by renewal - so even #2208 stays unmerged until then. The card is closed as duplicate so it does not redispatch; if #2208 stalls permanently, the card gets re-opened with a STEP-0 supersede note, not rebuilt blind.

@gitar-bot

gitar-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

@jaylfc jaylfc closed this Aug 3, 2026
@jaylfc
jaylfc deleted the exec/tsk-nslyhc branch August 3, 2026 03:46
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add per-identity iat cutoff to enable registry token rotation/invalidation

🐞 Bug fix ✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Add per-agent token_min_iat cutoff so old registry JWTs can be invalidated.
• Enforce cutoff during agent-scope verification, returning 401 "token superseded".
• Add admin-only rotate-token endpoint to bump cutoff and audit the action.
Diagram

graph TD
  A{{"Admin/operator"}} --> B["Rotate-token API"] --> C["AgentRegistryStore"] --> D[("agent_registry DB")]
  E{{"Agent with JWT"}} --> F["AgentTokenAuth"] --> C --> D
  B --> G["Governance audit"]
  F --> H["401 superseded"]

  subgraph Legend
    direction LR
    _ext{{"Caller"}} ~~~ _api["API/Service"] ~~~ _db[("Database")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Token version claim (monotonic counter)
  • ➕ Avoids timestamp format/unit edge cases (string vs int, epoch vs ISO).
  • ➕ Clear semantics: accept only tokens where version >= stored version.
  • ➖ Requires minting logic changes to include a version claim.
  • ➖ Still needs a persisted per-identity field and update path (similar complexity).
2. Per-identity signing keys (rotate key to revoke)
  • ➕ Instant invalidation of all old tokens by changing the verifying key.
  • ➕ Removes the need for iat/cutoff checks in the auth path.
  • ➖ More complex key management (many keys vs one).
  • ➖ Requires resolving the correct public key per identity during verification.
3. Token blacklist / jti revocation list
  • ➕ Precisely revoke a single compromised token instead of all tokens.
  • ➕ Works even without rotating an identity’s entire token set.
  • ➖ Stateful lookup on every request; storage and operational overhead.
  • ➖ Unbounded growth unless aggressively GC’d; more failure modes.

Recommendation: The PR’s iat-cutoff design is a strong balance of simplicity and capability: it enables per-identity bulk invalidation with a single additional field and a fast check in the shared verifier. The main thing to validate is consistency of types/units between token iat (typically epoch seconds int) and stored token_min_iat (currently written as an ISO string in the rotate route) so the comparison is reliable.

Files changed (4) +323 / -4

Enhancement (2) +114 / -4
agent_registry_store.pyAdd token_min_iat column, migration v5, and bump helper +57/-4

Add token_min_iat column, migration v5, and bump helper

• Extends the agent_registry schema with 'token_min_iat' (default 0) and adds an idempotent migration to add the column to existing DBs. Updates registration inserts to populate the new column and adds 'bump_token_min_iat()' to advance the cutoff.

tinyagentos/agent_registry_store.py

agent_registry.pyAdd admin rotate-token endpoint to bump cutoff and audit +57/-0

Add admin rotate-token endpoint to bump cutoff and audit

• Adds POST '/api/agents/registry/{canonical_id}/rotate-token' for admins to bump 'token_min_iat' to "now", invalidating prior tokens. Verifies the caller holds a valid token for that identity and records a governance audit event.

tinyagentos/routes/agent_registry.py

Bug fix (1) +6 / -0
agent_token_auth.pyEnforce per-identity iat cutoff during agent scope verification +6/-0

Enforce per-identity iat cutoff during agent scope verification

• Adds a guard that returns 401 "token superseded" when the JWT payload 'iat' is older than the stored 'token_min_iat' for that agent’s registry record.

tinyagentos/agent_token_auth.py

Tests (1) +203 / -0
test_token_rotation.pyAdd token rotation tests (iat cutoff) and route wiring checks +203/-0

Add token rotation tests (iat cutoff) and route wiring checks

• Introduces a new async test module to validate that old tokens are rejected after cutoff bump and that the rotate-token endpoint exists/rotates successfully. Includes a migration-safety check asserting default 'token_min_iat=0' for existing records.

test_token_rotation.py

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📜 Skill insights (2)

Context used
✅ Compliance rules (platform): 35 rules

Grey Divider


Action required

1. token_min_iat in SCHEMA 📜 Skill insight ≡ Correctness
Description
token_min_iat is added to SCHEMA while also being introduced via a migration, which violates the
rule that SCHEMA must not reference migration-added columns. This can break first-open/migration
ordering guarantees and reintroduce upgrade-path fragility.
Code

tinyagentos/agent_registry_store.py[R48-49]

+    status          TEXT    NOT NULL DEFAULT 'active',
+    token_min_iat   INTEGER NOT NULL DEFAULT 0
Relevance

●●● Strong

Team previously moved migration-dependent items out of SCHEMA to post-init to avoid upgrade ordering
breaks (PR #1853).

PR-#1853

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2185190 forbids SCHEMA from referencing any column introduced via migrations. The
updated SCHEMA includes token_min_iat, while _migration_v5_add_token_min_iat also adds that
column via ALTER TABLE, violating the rule.

tinyagentos/agent_registry_store.py[35-50]
tinyagentos/agent_registry_store.py[209-225]
Skill: taos-development-skill

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`token_min_iat` is present in `SCHEMA` even though it is also added via `_migration_v5_add_token_min_iat`. Per compliance, any migration-added column must not appear in `SCHEMA`; it should be retrofitted in `_post_init` via a guarded `ALTER TABLE`.

## Issue Context
This store already follows this pattern for other migration-added elements (e.g., `ACTIVE_HANDLE_INDEX` avoids `SCHEMA` because `status` is added by migration). `token_min_iat` should follow the same invariant.

## Fix Focus Areas
- tinyagentos/agent_registry_store.py[35-51]
- tinyagentos/agent_registry_store.py[209-226]
- tinyagentos/agent_registry_store.py[463-486]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. IAT cutoff type mismatch 🐞 Bug ≡ Correctness
Description
token_min_iat is stored as an INTEGER cutoff but rotation writes ISO8601 strings into it, and
_verify_agent_scope compares JWT iat (int) to token_min_iat without coercion. This can raise a
TypeError (500) during auth for rotated identities, breaking token rotation enforcement and
potentially taking down agent-authenticated endpoints for that identity.
Code

tinyagentos/agent_token_auth.py[R113-117]

+    # Token iat cutoff: reject if iat < token_min_iat (token superseded)
+    payload_iat = payload.get("iat")
+    token_min_iat = record.get("token_min_iat")
+    if payload_iat is not None and token_min_iat is not None and payload_iat < token_min_iat:
+        raise HTTPException(status_code=401, detail="token superseded")
Relevance

●●● Strong

Similar correctness/type-safety fixes (normalize/validate persisted types to avoid runtime errors)
have been accepted (PR #1542).

PR-#1542

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The registry JWT iat is minted as an integer, the schema defines token_min_iat as INTEGER, but
rotation writes an ISO string and the auth layer compares the two values without normalization; the
store also returns DB values without type coercion, making a TypeError on < likely after rotation.

tinyagentos/agent_registry_store.py[35-50]
tinyagentos/agent_registry_store.py[299-346]
tinyagentos/agent_registry_store.py[429-436]
tinyagentos/agent_registry_store.py[1040-1065]
tinyagentos/routes/agent_registry.py[502-505]
tinyagentos/agent_token_auth.py[107-118]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`token_min_iat` is intended to be compared to the JWT `iat` claim, which is minted as an integer (epoch seconds). The PR writes ISO8601 strings into `token_min_iat` and compares them directly to the integer `iat`, which can trigger a runtime `TypeError` and break authentication.

## Issue Context
- Schema declares `token_min_iat INTEGER`.
- Tokens are minted with `iat = int(time.time())`.
- Rotation writes `datetime.now(...).isoformat()` into `token_min_iat`.
- Auth compares `payload_iat < token_min_iat` without normalizing types.

## Fix Focus Areas
- tinyagentos/agent_registry_store.py[35-50]
- tinyagentos/agent_registry_store.py[299-346]
- tinyagentos/agent_registry_store.py[1040-1065]
- tinyagentos/agent_token_auth.py[107-118]
- tinyagentos/routes/agent_registry.py[489-516]

## Implementation notes
- Change `bump_token_min_iat(..., ts: str)` to accept an `int` cutoff (epoch seconds).
- In `rotate_token`, set `now_iat = int(time.time())` (or equivalent) and store that.
- In `_verify_agent_scope`, coerce both `payload["iat"]` and `record["token_min_iat"]` to `int` (fail closed with 401 if malformed).
- Consider making the DB update monotonic (e.g., only update when new cutoff > existing cutoff) to prevent accidentally lowering the cutoff and re-validating old tokens.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Rotate-token NameError 🐞 Bug ≡ Correctness
Description
rotate_token calls datetime.now(timezone.utc) but the module does not import datetime/timezone at
top-level, so eligible rotation requests will raise NameError and return 500. This makes the new
rotation endpoint unusable on the success path.
Code

tinyagentos/routes/agent_registry.py[R503-504]

+    now = datetime.now(timezone.utc).isoformat()
+    updated = await store.bump_token_min_iat(canonical_id, now)
Relevance

●●● Strong

Repo consistently accepts fixes preventing unhandled exceptions/500s in routes (e.g., catching
ImportError) (PR #419).

PR-#419

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The route module imports do not include datetime/timezone, yet rotate_token uses them; the only
datetime import is a local import inside a separate function, which doesn’t affect rotate_token’s
scope.

tinyagentos/routes/agent_registry.py[23-35]
tinyagentos/routes/agent_registry.py[502-505]
tinyagentos/routes/agent_registry.py[606-607]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`rotate_token()` references `datetime` and `timezone`, but `tinyagentos/routes/agent_registry.py` does not import them at module scope. This will crash with `NameError` when the route reaches the cutoff update.

## Issue Context
There is a `from datetime import datetime, timezone` inside a different function, but that does not make the names available in `rotate_token()`.

## Fix Focus Areas
- tinyagentos/routes/agent_registry.py[23-35]
- tinyagentos/routes/agent_registry.py[502-505]
- tinyagentos/routes/agent_registry.py[606-607]

## Implementation notes
- Add `from datetime import datetime, timezone` to the module imports, OR
- Preferably, if switching token_min_iat to epoch seconds, compute `now_iat = int(time.time())` and `import time` instead.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Rotate-token token mismatch 🐞 Bug ⛨ Security
Description
rotate_token treats any successful _check_feed_token() result as proof the caller holds the target
identity’s token, but it never verifies the returned canonical_id matches the path canonical_id. An
admin with a feeds_read token for agent A can rotate agent B, contradicting the route’s documented
“must hold the token for this identity” constraint.
Code

tinyagentos/routes/agent_registry.py[R493-496]

+    if not await _check_feed_token(request):
+        # Fallback to the more basic check_agent_identity which doesn't
+        # require a scope grant.
+        if await check_agent_identity(request) != canonical_id:
Relevance

●● Moderate

Mixed signals: ID-matching guards often accepted (PR #260), but similar auth identity-binding
hardening was rejected (PR #1662).

PR-#260
PR-#1662

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
_check_feed_token returns a canonical_id for the presented Bearer token, but rotate_token only
checks whether it returned a truthy value, and only enforces canonical_id equality in the fallback
path (when no Authorization header is present).

tinyagentos/routes/agent_registry.py[200-210]
tinyagentos/routes/agent_registry.py[489-500]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The rotate-token route intends to require the caller to present a valid token for *the same* `canonical_id` being rotated. Today it only checks truthiness of `_check_feed_token()` and skips verifying the token’s `sub` matches the path parameter, enabling cross-identity rotation as long as the caller has *any* valid feeds_read token.

## Issue Context
`_check_feed_token()` returns the canonical_id encoded in whatever Bearer token is provided. `rotate_token()` currently does not compare that returned value to the target `canonical_id`.

## Fix Focus Areas
- tinyagentos/routes/agent_registry.py[200-210]
- tinyagentos/routes/agent_registry.py[489-501]

## Implementation notes
- Capture the returned canonical_id from `_check_feed_token(request)`; if non-None, require it equals the path `canonical_id`.
- Alternatively (simpler), remove `_check_feed_token` from this route and always use `check_agent_identity(request)` and require equality to the path `canonical_id` (since rotate-token should not require any scope grant).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

5. rotate_token lacks response_model 📜 Skill insight ✧ Quality
Description
The new rotate_token route returns raw dict/JSONResponse payloads without a declared Pydantic
response_model. This reduces schema validation and makes the API contract easier to drift.
Code

tinyagentos/routes/agent_registry.py[R462-463]

+@router.post("/api/agents/registry/{canonical_id}/rotate-token")
+async def rotate_token(
Relevance

● Weak

Similar request to add FastAPI response_model/Pydantic responses was explicitly rejected by
reviewers (PR #2122).

PR-#2122

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2185155 requires route payloads (including responses) to use Pydantic models
instead of raw dicts. The new rotate_token handler has no response_model and returns
JSONResponse({...}) / updated directly.

tinyagentos/routes/agent_registry.py[462-517]
Skill: taos-development-skill

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`POST /api/agents/registry/{canonical_id}/rotate-token` returns untyped payloads and does not declare a `response_model`, contrary to the requirement that route request/response payloads use Pydantic models.

## Issue Context
This module already uses Pydantic models for request bodies. The new route should similarly define/declare a response model for the returned updated registry record (or at least the subset of fields it returns).

## Fix Focus Areas
- tinyagentos/routes/agent_registry.py[46-120]
- tinyagentos/routes/agent_registry.py[462-517]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment on lines +48 to +49
status TEXT NOT NULL DEFAULT 'active',
token_min_iat INTEGER NOT NULL DEFAULT 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. token_min_iat in schema 📜 Skill insight ≡ Correctness

token_min_iat is added to SCHEMA while also being introduced via a migration, which violates the
rule that SCHEMA must not reference migration-added columns. This can break first-open/migration
ordering guarantees and reintroduce upgrade-path fragility.
Agent Prompt
## Issue description
`token_min_iat` is present in `SCHEMA` even though it is also added via `_migration_v5_add_token_min_iat`. Per compliance, any migration-added column must not appear in `SCHEMA`; it should be retrofitted in `_post_init` via a guarded `ALTER TABLE`.

## Issue Context
This store already follows this pattern for other migration-added elements (e.g., `ACTIVE_HANDLE_INDEX` avoids `SCHEMA` because `status` is added by migration). `token_min_iat` should follow the same invariant.

## Fix Focus Areas
- tinyagentos/agent_registry_store.py[35-51]
- tinyagentos/agent_registry_store.py[209-226]
- tinyagentos/agent_registry_store.py[463-486]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +113 to +117
# Token iat cutoff: reject if iat < token_min_iat (token superseded)
payload_iat = payload.get("iat")
token_min_iat = record.get("token_min_iat")
if payload_iat is not None and token_min_iat is not None and payload_iat < token_min_iat:
raise HTTPException(status_code=401, detail="token superseded")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Iat cutoff type mismatch 🐞 Bug ≡ Correctness

token_min_iat is stored as an INTEGER cutoff but rotation writes ISO8601 strings into it, and
_verify_agent_scope compares JWT iat (int) to token_min_iat without coercion. This can raise a
TypeError (500) during auth for rotated identities, breaking token rotation enforcement and
potentially taking down agent-authenticated endpoints for that identity.
Agent Prompt
## Issue description
`token_min_iat` is intended to be compared to the JWT `iat` claim, which is minted as an integer (epoch seconds). The PR writes ISO8601 strings into `token_min_iat` and compares them directly to the integer `iat`, which can trigger a runtime `TypeError` and break authentication.

## Issue Context
- Schema declares `token_min_iat INTEGER`.
- Tokens are minted with `iat = int(time.time())`.
- Rotation writes `datetime.now(...).isoformat()` into `token_min_iat`.
- Auth compares `payload_iat < token_min_iat` without normalizing types.

## Fix Focus Areas
- tinyagentos/agent_registry_store.py[35-50]
- tinyagentos/agent_registry_store.py[299-346]
- tinyagentos/agent_registry_store.py[1040-1065]
- tinyagentos/agent_token_auth.py[107-118]
- tinyagentos/routes/agent_registry.py[489-516]

## Implementation notes
- Change `bump_token_min_iat(..., ts: str)` to accept an `int` cutoff (epoch seconds).
- In `rotate_token`, set `now_iat = int(time.time())` (or equivalent) and store that.
- In `_verify_agent_scope`, coerce both `payload["iat"]` and `record["token_min_iat"]` to `int` (fail closed with 401 if malformed).
- Consider making the DB update monotonic (e.g., only update when new cutoff > existing cutoff) to prevent accidentally lowering the cutoff and re-validating old tokens.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +493 to +496
if not await _check_feed_token(request):
# Fallback to the more basic check_agent_identity which doesn't
# require a scope grant.
if await check_agent_identity(request) != canonical_id:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

4. Rotate-token token mismatch 🐞 Bug ⛨ Security

rotate_token treats any successful _check_feed_token() result as proof the caller holds the target
identity’s token, but it never verifies the returned canonical_id matches the path canonical_id. An
admin with a feeds_read token for agent A can rotate agent B, contradicting the route’s documented
“must hold the token for this identity” constraint.
Agent Prompt
## Issue description
The rotate-token route intends to require the caller to present a valid token for *the same* `canonical_id` being rotated. Today it only checks truthiness of `_check_feed_token()` and skips verifying the token’s `sub` matches the path parameter, enabling cross-identity rotation as long as the caller has *any* valid feeds_read token.

## Issue Context
`_check_feed_token()` returns the canonical_id encoded in whatever Bearer token is provided. `rotate_token()` currently does not compare that returned value to the target `canonical_id`.

## Fix Focus Areas
- tinyagentos/routes/agent_registry.py[200-210]
- tinyagentos/routes/agent_registry.py[489-501]

## Implementation notes
- Capture the returned canonical_id from `_check_feed_token(request)`; if non-None, require it equals the path `canonical_id`.
- Alternatively (simpler), remove `_check_feed_token` from this route and always use `check_agent_identity(request)` and require equality to the path `canonical_id` (since rotate-token should not require any scope grant).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +503 to +504
now = datetime.now(timezone.utc).isoformat()
updated = await store.bump_token_min_iat(canonical_id, now)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

5. Rotate-token nameerror 🐞 Bug ≡ Correctness

rotate_token calls datetime.now(timezone.utc) but the module does not import datetime/timezone at
top-level, so eligible rotation requests will raise NameError and return 500. This makes the new
rotation endpoint unusable on the success path.
Agent Prompt
## Issue description
`rotate_token()` references `datetime` and `timezone`, but `tinyagentos/routes/agent_registry.py` does not import them at module scope. This will crash with `NameError` when the route reaches the cutoff update.

## Issue Context
There is a `from datetime import datetime, timezone` inside a different function, but that does not make the names available in `rotate_token()`.

## Fix Focus Areas
- tinyagentos/routes/agent_registry.py[23-35]
- tinyagentos/routes/agent_registry.py[502-505]
- tinyagentos/routes/agent_registry.py[606-607]

## Implementation notes
- Add `from datetime import datetime, timezone` to the module imports, OR
- Preferably, if switching token_min_iat to epoch seconds, compute `now_iat = int(time.time())` and `import time` instead.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

)

before = record.get("token_min_iat")
now = datetime.now(timezone.utc).isoformat()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: token_min_iat column is INTEGER, but rotate_token stores an ISO datetime string. This causes TypeError in _verify_agent_scope when comparing payload_iat (int) with the stored string, returning 500 on every authenticated request after rotation.

Suggested change
now = datetime.now(timezone.utc).isoformat()
now = int(datetime.now(timezone.utc).timestamp())

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

# Token rotation helper
# ------------------------------------------------------------------

async def bump_token_min_iat(self, canonical_id: str, ts: str) -> dict:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: bump_token_min_iat type-hints ts: str, but it should accept an integer Unix timestamp to match the INTEGER column and JWT iat semantics.

Suggested change
async def bump_token_min_iat(self, canonical_id: str, ts: str) -> dict:
async def bump_token_min_iat(self, canonical_id: str, ts: int) -> dict:

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread test_token_rotation.py
import tempfile
import os

with tempfile.TemporaryDirectory() as tmp:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test

Comment thread test_token_rotation.py
# Since the fixture uses admin, we can't easily test non-admin
# This test ensures the route has the admin check in place

# Verify the route exists by checking the router

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test

Comment thread test_token_rotation.py
# Rotate the token (bump token_min_iat)
# Use admin client via direct store method
store = client._app.state.agent_registry
now_ts = datetime.now(timezone.utc).isoformat()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test

# an active token with iat >= current token_min_iat before the bump).
# Use the shared verifier to ensure any token (including the one that will
# be minted next) would authenticate.
if not await _check_feed_token(request):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test

if not await _check_feed_token(request):
# Fallback to the more basic check_agent_identity which doesn't
# require a scope grant.
if await check_agent_identity(request) != canonical_id:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: check_agent_identity is used here but is not imported at the top of the file. This causes NameError at runtime when the rotate-token route is hit without a registry_feeds_read grant. Add check_agent_identity to the import from tinyagentos.agent_token_auth.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 7 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 3
WARNING 4
Issue Details (click to expand)

CRITICAL

File Line Issue
tinyagentos/routes/agent_registry.py 503 token_min_iat column is INTEGER, but rotate_token stores an ISO datetime string, causing TypeError in _verify_agent_scope when comparing payload_iat (int) with the stored string.
tinyagentos/routes/agent_registry.py 496 check_agent_identity is used here but is not imported at the top of the file, causing NameError at runtime.
tinyagentos/agent_registry_store.py 1040 bump_token_min_iat type-hints ts: str, but it should accept an integer Unix timestamp to match the INTEGER column and JWT iat semantics.

WARNING

File Line Issue
test_token_rotation.py 166 with tempfile.TemporaryDirectory() as tmp: creates an unused temp directory; load_or_create_signing_keypair(tmp_path) uses the pytest fixture instead of tmp, and returned values are never used. No actual token verification is performed.
test_token_rotation.py 196 Test explicitly admits it cannot verify non-admin rejection because the fixture is admin. It only checks route existence, not authorization behavior.
test_token_rotation.py 118 now_ts = datetime.now(timezone.utc).isoformat() passes an ISO string to bump_token_min_iat, matching the buggy production behavior.
tinyagentos/routes/agent_registry.py 493 Admin route requires the caller to also hold a valid Bearer token for the target identity, unlike all other admin routes. An admin with only a session cookie cannot rotate tokens.
Files Reviewed (4 files)
  • test_token_rotation.py - 4 issues
  • tinyagentos/agent_registry_store.py - 1 issue
  • tinyagentos/agent_token_auth.py
  • tinyagentos/routes/agent_registry.py - 2 issues

Fix these issues in Kilo Cloud


Reviewed by step-3.7-flash · Input: 105.7K · Output: 37.4K · Cached: 1.4M

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant