Skip to content

tsk-rll2pj [OPEN] Fix-forward PR 2202 (S2A follow-ups): slug fallbac - #2225

Merged
jaylfc merged 4 commits into
devfrom
exec/tsk-rll2pj
Aug 2, 2026
Merged

tsk-rll2pj [OPEN] Fix-forward PR 2202 (S2A follow-ups): slug fallbac#2225
jaylfc merged 4 commits into
devfrom
exec/tsk-rll2pj

Conversation

@jaylfc

@jaylfc jaylfc commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Autonomous build of board card tsk-rll2pj.

Files:
tests/test_agent_registry_store.py | 69 +++++++++++++++++++++++++++
tests/test_routes_share_destinations.py | 83 +++++++++++++++++++++++++++++++++
tinyagentos/agent_registry_store.py | 40 ++++++++++++++++
tinyagentos/routes/share.py | 4 ++
4 files changed, 196 insertions(+)


Summary by Gitar

  • Agent Registry:
    • Added get_by_slug lookup method to AgentRegistryStore with strict tail matching to prevent false prefix collisions
    • Updated list_share_destinations route to fall back to get_by_slug when DM channel members are stored as bare slugs
  • Testing:
    • Added comprehensive unit and integration tests covering slug lookup, collision suffixes, inactive filtering, and boundary constraints

This will update automatically on new commits.

@coderabbitai

coderabbitai Bot commented Aug 2, 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: 13 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: dcdc19e6-f49f-4e99-bc42-ecde10021f6e

📥 Commits

Reviewing files that changed from the base of the PR and between 8de1e92 and 4269efa.

📒 Files selected for processing (6)
  • docs/agent-coordination.md
  • tests/test_agent_registry_store.py
  • tests/test_routes_share_destinations.py
  • tinyagentos/agent_registry_store.py
  • tinyagentos/routes/__init__.py
  • tinyagentos/routes/share.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.

@gitar-bot

gitar-bot Bot commented Aug 2, 2026

Copy link
Copy Markdown

Gitar is working

Gitar

@jaylfc

jaylfc commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Code layer reviewed and it is good - the GLOB tail bound with the 2-hex collision suffix handling is better than what I asked for, and the negative-test matrix is exactly right. Two items short of the original PR 2202 scope before this can supersede it: (1) merge origin/exec/tsk-f4nt53 into this branch (or cherry-pick its routes/init.py CSRF-dep hunk), and (2) add the docs/agent-coordination.md section for GET /api/share/destinations with the CORRECTED claims: caller is require_device only, discovery-only (a device token cannot write to the endpoints the old draft listed), and coverage is registry-backed agents only - plain deployed agents have no registry row and still resolve nothing. Re-push to this branch, do not open a new PR. On your push + green CI this merges and 2202 closes as superseded.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix share destinations when DM members are stored as bare agent slugs

🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Add bounded slug lookup in agent registry to resolve bare-slug DM members.
• Update share destinations endpoint to fall back from canonical_id to slug lookup.
• Add regression tests for slug collisions, inactive agents, and DM resolution boundaries.
Diagram

graph TD
  client(("Paired device")) --> route["GET /api/share/destinations"] --> chstore["ChatChannelsStore"] --> route
  route --> registry["AgentRegistryStore"] --> db[("agent_registry (SQLite)")] --> registry
  route --> resp["Destinations JSON"]

  subgraph Legend
    direction LR
    _actor(("Client")) ~~~ _api["API endpoint"] ~~~ _db[("Database")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Normalize DM channel members to canonical_id (migration + write-path fix)
  • ➕ Eliminates ambiguity and avoids pattern-based queries entirely
  • ➕ Allows direct primary-key lookup (fast, index-friendly)
  • ➕ Keeps identifier semantics consistent across the system
  • ➖ Requires migrating existing channels and updating all creation paths
  • ➖ Harder rollout if multiple clients/components depend on slug members
  • ➖ Backfill needs careful handling for archived/orphan channels
2. Persist slug as a first-class column in agent_registry (with index)
  • ➕ Enables exact-match slug lookup without relying on canonical_id parsing
  • ➕ Supports future canonical_id format changes more safely
  • ➕ Can be indexed for efficient slug queries
  • ➖ Schema migration required (including backfill)
  • ➖ Must keep slug and canonical_id derivation consistent forever
3. Two-phase lookup: fetch candidates by prefix then validate in Python
  • ➕ Avoids relying on SQLite GLOB semantics
  • ➕ Can precisely enforce the canonical-id tail regex already defined elsewhere
  • ➖ More code and more DB rows scanned under high cardinality
  • ➖ Still depends on canonical_id encoding; just shifts validation to app code

Recommendation: The PR’s approach (bounded tail match via SQLite GLOB + endpoint fallback) is the best fix-forward: it restores behavior for existing DM channels that stored bare slugs while explicitly preventing prefix collisions (e.g., "alpha" vs "alpha-beta-"). Longer-term, consider normalizing stored DM members to canonical_id (or adding a slug column) to remove canonical_id-format coupling from lookup logic.

Files changed (4) +196 / -0

Bug fix (2) +44 / -0
agent_registry_store.pyAdd get_by_slug with bounded canonical-id tail matching +40/-0

Add get_by_slug with bounded canonical-id tail matching

• Adds AgentRegistryStore.get_by_slug() to resolve agents when only the slug is known (as stored in some DM channel members). Uses a bounded SQLite GLOB pattern anchored on the canonical timestamp tail (plus optional collision suffix) to avoid false prefix matches; defaults to active-only unless status=None.

tinyagentos/agent_registry_store.py

share.pyFall back to slug lookup when resolving DM channel members +4/-0

Fall back to slug lookup when resolving DM channel members

• Updates list_share_destinations to try registry.get(member) first and, if not found, fall back to registry.get_by_slug(member). This ensures agent chat destinations appear even when DM members are stored as bare slugs.

tinyagentos/routes/share.py

Tests (2) +152 / -0
test_agent_registry_store.pyAdd unit tests for AgentRegistryStore.get_by_slug semantics +69/-0

Add unit tests for AgentRegistryStore.get_by_slug semantics

• Introduces a focused test class covering bare-slug resolution, preventing prefix collisions, handling collision suffixes, and default filtering of inactive agents. Also asserts the method raises when the store is not initialized.

tests/test_agent_registry_store.py

test_routes_share_destinations.pyAdd integration tests for bare-slug DM members in share destinations +83/-0

Add integration tests for bare-slug DM members in share destinations

• Adds regression tests ensuring /api/share/destinations resolves DM members stored as bare slugs and does not incorrectly match longer slugs. Includes a shared helper to initialize a fresh registry and register a paired device for authenticated calls.

tests/test_routes_share_destinations.py

@jaylfc

jaylfc commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

nemotron-ultra-orB review

VERDICT: Clean implementation with comprehensive tests; no blocking issues found.

  • The get_by_slug method correctly uses a bounded GLOB pattern ({slug}-YYYYMMDD-HHMMSS*) that prevents prefix matches (e.g., alpha won't match alpha-beta-...) while allowing the optional 2-hex collision suffix.
  • Fallback logic in share.py is correct: try canonical_id first, then slug lookup.
  • Test coverage is thorough: bare slug, prefix boundary, collision suffix, inactive filtering, uninitialized store, and integration tests for the share destinations route.
  • Minor note: slug is interpolated directly into the GLOB pattern; since slugs are system-generated via mint_canonical_id (lowercased, spaces→hyphens), injection risk is negligible, but a comment or assertion could document this assumption.

Automated first-pass review by the nemotron-ultra-orB lane. The lead still reviews before merge.

@jaylfc

jaylfc commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

nemotron-super review

VERDICT: Potential correctness bug: get_by_slug does not escape GLOB special characters in slug, causing incorrect matching.

  • tinyagentos/agent_registry_store.py:539

Automated first-pass review by the nemotron-super lane. The lead still reviews before merge.

if agent is None:
# DM channels store the agent slug (not its canonical_id)
# as the member, so slug members need a slug lookup.
agent = await registry.get_by_slug(member)

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: False positive risk: get_by_slug fallback can incorrectly resolve user_ids as agents

When a DM channel contains a non-agent member (e.g., another user's user_id), registry.get(member) returns None, then get_by_slug(member) is called. If member happens to match the GLOB pattern {member}-[YYYYMMDD]-[HHMMSS]* for an existing agent (e.g., a user_id like "user-1" with a registered agent of the same slug), the user is incorrectly treated as an agent destination. Consider validating that member looks like a bare slug before applying the fallback.


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

).fetchone()
return _row_to_dict(row) if row else None

async def get_by_slug(self, slug: str, *, status: str = "active") -> Optional[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.

SUGGESTION: Type annotation status: str should be Optional[str]

The status parameter accepts None (to match any status) but is annotated as str. This is inconsistent with the docstring and could mislead type checkers.

Suggested change
async def get_by_slug(self, slug: str, *, status: str = "active") -> Optional[dict]:
async def get_by_slug(self, slug: str, *, status: Optional[str] = "active") -> Optional[dict]:

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

# same-second slug collision, a -{2hex} suffix). GLOBing the 8-6 digit
# tail pins the slug to its own canonical-id boundary.
pattern = (
f"{slug}-"

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: GLOB special characters in slug are not escaped

If get_by_slug is called with a slug containing *, ?, [, or ], those characters are interpreted as GLOB wildcards rather than literals, producing incorrect matches. While _slugify produces safe values for internally generated slugs, this public method accepts arbitrary input and should escape GLOB metacharacters.


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

@kilo-code-bot

kilo-code-bot Bot commented Aug 2, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
WARNING 2
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
tinyagentos/routes/share.py 66 False positive risk: get_by_slug fallback can incorrectly resolve user_ids as agents
tinyagentos/routes/share.py 58 Broad except Exception around auth.find_user(member) could silently swallow errors

SUGGESTION

File Line Issue
tinyagentos/agent_registry_store.py 542 Type annotation status: str should be Optional[str]
Files Reviewed (2 files)
  • tinyagentos/routes/share.py - 2 issues
  • tinyagentos/agent_registry_store.py - 1 issue

Fix these issues in Kilo Cloud

Previous Review Summary (commit 7add045)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 7add045)

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
WARNING 2
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
tinyagentos/routes/share.py 56 False positive risk: get_by_slug fallback can incorrectly resolve user_ids as agents
tinyagentos/agent_registry_store.py 563 GLOB special characters in slug are not escaped

SUGGESTION

File Line Issue
tinyagentos/agent_registry_store.py 542 Type annotation status: str should be Optional[str]
Files Reviewed (4 files)
  • tinyagentos/routes/share.py - 1 issue
  • tinyagentos/agent_registry_store.py - 2 issues
  • tests/test_agent_registry_store.py
  • tests/test_routes_share_destinations.py

Fix these issues in Kilo Cloud


Reviewed by step-3.7-flash · Input: 72K · Output: 29.9K · Cached: 535.6K

@qodo-code-review

qodo-code-review Bot commented Aug 2, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 35 rules

Grey Divider


Action required

1. Unsafe slug GLOB matching 🐞 Bug ⛨ Security
Description
AgentRegistryStore.get_by_slug builds a SQLite GLOB pattern from the provided slug and appends "*",
which matches arbitrary suffixes beyond the documented optional 2-hex collision suffix; it also does
not validate/escape GLOB metacharacters or scope by user_id. This can resolve the wrong agent
(including cross-user) when slugs collide or when a crafted member contains GLOB wildcards like "*"
or "?".
Code

tinyagentos/agent_registry_store.py[R562-578]

+        pattern = (
+            f"{slug}-"
+            "[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]"
+            "-"
+            "[0-9][0-9][0-9][0-9][0-9][0-9]"
+            "*"
+        )
+        if status is None:
+            cursor = await self._db.execute(
+                "SELECT * FROM agent_registry WHERE canonical_id GLOB ? ORDER BY id LIMIT 1",
+                (pattern,),
+            )
+        else:
+            cursor = await self._db.execute(
+                "SELECT * FROM agent_registry WHERE canonical_id GLOB ? AND status = ? ORDER BY id LIMIT 1",
+                (pattern, status),
+            )
Relevance

●●● Strong

Repo frequently accepts input-sanitization/authorization hardening; escaping GLOB metacharacters and
scoping by user_id fits that pattern.

PR-#404
PR-#260

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Canonical-id minting only adds an optional 2-hex suffix on collision, and the repo has an
end-anchored canonical-id regex; the new * makes the DB match looser than that contract.
Additionally, registry rows are user-scoped (user_id column), and chat channels can accept
arbitrary members, so unvalidated slug patterns can be introduced and/or resolve across users.

tinyagentos/agent_registry_store.py[35-49]
tinyagentos/agent_registry_store.py[356-372]
tinyagentos/agent_registry_store.py[438-483]
tinyagentos/agent_registry_store.py[542-580]
tinyagentos/chat/orphan_reconcile.py[30-34]
tinyagentos/routes/chat_admin.py[9-22]

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

### Issue description
`AgentRegistryStore.get_by_slug()`:
- Uses `canonical_id GLOB ?` with a pattern ending in `*`, which is broader than the canonical format it documents.
- Interpolates `slug` into a GLOB pattern without validating/escaping metacharacters (`*`, `?`, `[...]`), allowing unintended matches if a channel member is not a real slug.
- Does not support scoping the lookup by `user_id`, even though registry rows carry `user_id`.

### Issue Context
Canonical IDs are minted as `{slug}-{YYYYMMDD}-{HHMMSS}` with an optional `-{2hex}` collision suffix. The repo already has an end-anchored regex for this canonical format. Also, chat channels can be created with arbitrary `members` via the admin route, so unsafe member strings can enter the system.

### Fix Focus Areas
- tinyagentos/agent_registry_store.py[542-580]
- tinyagentos/agent_registry_store.py[356-372]
- tinyagentos/agent_registry_store.py[438-505]
- tinyagentos/chat/orphan_reconcile.py[30-34]
- tinyagentos/routes/chat_admin.py[9-22]

### What to change
1. **Validate slug input** before building any pattern (e.g., allow only `^[a-z0-9]+(?:-[a-z0-9]+)*$`); if invalid, return `None`.
2. **Tighten the match** to only canonical forms:
  - Either use two exact GLOB patterns (no trailing `*`):
    - `{slug}-YYYYMMDD-HHMMSS`
    - `{slug}-YYYYMMDD-HHMMSS-[0-9a-f][0-9a-f]`
  - Or query by prefix and then apply the end-anchored canonical regex in Python to ensure the decoded slug equals the requested slug.
3. Add an optional `user_id` filter to `get_by_slug(..., user_id: str | None = None)` and include `AND user_id = ?` when provided.
4. Update the call site in `list_share_destinations` to pass the current user id when available.

### Acceptance criteria
- `get_by_slug('alpha')` cannot match `alpha-YYYYMMDD-HHMMSS-extra`.
- A member like `a*` does not broaden matches.
- In multi-user data, slug lookup can be constrained to the current user when needed.

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


2. Human DM misclassified ✓ Resolved 🐞 Bug ≡ Correctness
Description
In list_share_destinations, any non-user channel member that isn’t a canonical_id is now resolved
via get_by_slug(), so a user↔human DM member like "bob" can be incorrectly treated as an active
agent chat when an agent slug collides with that human handle. This can surface wrong share
destinations (wrong label/id) and can misroute shares to an unintended conversation target.
Code

tinyagentos/routes/share.py[R53-56]

+                    if agent is None:
+                        # DM channels store the agent slug (not its canonical_id)
+                        # as the member, so slug members need a slug lookup.
+                        agent = await registry.get_by_slug(member)
Relevance

●● Moderate

Potential DM ambiguity is plausible but semantic; no close precedent on share-destination member
classification collisions.

PR-#280

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new fallback applies to any member string, but the codebase explicitly documents/tests that
user↔human DMs share the same member shape as agent DMs. Therefore, slug fallback can re-interpret a
human handle as an agent slug when names collide.

tinyagentos/routes/share.py[40-66]
tinyagentos/routes/agents.py[688-705]
tinyagentos/chat/orphan_reconcile.py[41-52]
tests/test_orphan_channel_reconcile.py[100-110]

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

### Issue description
`/api/share/destinations` falls back to `registry.get_by_slug(member)` for **any** unresolved channel member string. This makes user↔human DM handles eligible for agent resolution when a handle equals an agent slug, producing incorrect `agent_chat` destinations.

### Issue Context
Agent DMs and user↔human DMs can share the same `members` shape (e.g., `["user", "bob"]`). The deploy route creates agent DMs with a bare slug member, which is why the fallback exists—but the fallback needs a discriminator so it only runs for members that are known to represent agents.

### Fix Focus Areas
- tinyagentos/routes/share.py[40-66]
- tinyagentos/routes/agents.py[688-705]
- tinyagentos/chat/orphan_reconcile.py[41-69]

### What to change
1. Only attempt the slug fallback when the member is *known to be an agent slug* (e.g., present in `request.app.state.config.agents` names, or another authoritative allowlist), and/or when the channel is a DM created for agents.
2. Optionally, if you need registry-driven recognition (not config-driven), add a stricter classification helper similar to `orphan_reconcile._is_known_agent_member` and use it here.
3. Keep the existing `registry.get(member)` canonical-id path unchanged.

### Acceptance criteria
- A user↔human DM like `["user", "bob"]` does **not** appear as `kind="agent_chat"` unless `bob` is explicitly a known agent slug for that user/system.
- Bare-slug agent DMs created by deploy continue to resolve correctly.

ⓘ 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 thread tinyagentos/routes/share.py
Comment on lines +562 to +578
pattern = (
f"{slug}-"
"[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]"
"-"
"[0-9][0-9][0-9][0-9][0-9][0-9]"
"*"
)
if status is None:
cursor = await self._db.execute(
"SELECT * FROM agent_registry WHERE canonical_id GLOB ? ORDER BY id LIMIT 1",
(pattern,),
)
else:
cursor = await self._db.execute(
"SELECT * FROM agent_registry WHERE canonical_id GLOB ? AND status = ? ORDER BY id LIMIT 1",
(pattern, status),
)

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

2. Unsafe slug glob matching 🐞 Bug ⛨ Security

AgentRegistryStore.get_by_slug builds a SQLite GLOB pattern from the provided slug and appends "*",
which matches arbitrary suffixes beyond the documented optional 2-hex collision suffix; it also does
not validate/escape GLOB metacharacters or scope by user_id. This can resolve the wrong agent
(including cross-user) when slugs collide or when a crafted member contains GLOB wildcards like "*"
or "?".
Agent Prompt
### Issue description
`AgentRegistryStore.get_by_slug()`:
- Uses `canonical_id GLOB ?` with a pattern ending in `*`, which is broader than the canonical format it documents.
- Interpolates `slug` into a GLOB pattern without validating/escaping metacharacters (`*`, `?`, `[...]`), allowing unintended matches if a channel member is not a real slug.
- Does not support scoping the lookup by `user_id`, even though registry rows carry `user_id`.

### Issue Context
Canonical IDs are minted as `{slug}-{YYYYMMDD}-{HHMMSS}` with an optional `-{2hex}` collision suffix. The repo already has an end-anchored regex for this canonical format. Also, chat channels can be created with arbitrary `members` via the admin route, so unsafe member strings can enter the system.

### Fix Focus Areas
- tinyagentos/agent_registry_store.py[542-580]
- tinyagentos/agent_registry_store.py[356-372]
- tinyagentos/agent_registry_store.py[438-505]
- tinyagentos/chat/orphan_reconcile.py[30-34]
- tinyagentos/routes/chat_admin.py[9-22]

### What to change
1. **Validate slug input** before building any pattern (e.g., allow only `^[a-z0-9]+(?:-[a-z0-9]+)*$`); if invalid, return `None`.
2. **Tighten the match** to only canonical forms:
   - Either use two exact GLOB patterns (no trailing `*`):
     - `{slug}-YYYYMMDD-HHMMSS`
     - `{slug}-YYYYMMDD-HHMMSS-[0-9a-f][0-9a-f]`
   - Or query by prefix and then apply the end-anchored canonical regex in Python to ensure the decoded slug equals the requested slug.
3. Add an optional `user_id` filter to `get_by_slug(..., user_id: str | None = None)` and include `AND user_id = ?` when provided.
4. Update the call site in `list_share_destinations` to pass the current user id when available.

### Acceptance criteria
- `get_by_slug('alpha')` cannot match `alpha-YYYYMMDD-HHMMSS-extra`.
- A member like `a*` does not broaden matches.
- In multi-user data, slug lookup can be constrained to the current user when needed.

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

@jaylfc

jaylfc commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

nemotron-ultra-kilo review

VERDICT: Potential GLOB pattern injection in get_by_slug; missing test coverage for edge cases; seen deduplication uses slug not canonical_id.

  • tinyagentos/agent_registry_store.py:542slug is directly interpolated into a GLOB pattern via f-string without validation. If a caller passes a slug containing GLOB metacharacters (*, ?, [, ]), they can manipulate the pattern (e.g., slug="alpha*[0-9]" matches alpha-20260101-000000 AND alphax-20260101-000000). Current callers use system-controlled slugs, but the method is public and unguarded.

  • tinyagentos/agent_registry_store.py:542ORDER BY id LIMIT 1 returns the oldest row for a slug. If two agents share a base slug (same-second registration with collision suffixes -00, -01), only the first is ever returned. No test covers this collision scenario.

  • tinyagentos/routes/share.py:54seen.add(member) deduplicates by the slug (member), not agent["canonical_id"]. If two agents somehow share a slug (see above), the second is silently dropped. Should deduplicate by canonical_id for correctness.

  • tinyagentos/routes/share.py:57 — Destination id is the slug (member), not the canonical_id. Consumers expecting a stable canonical identifier get the slug instead. Inconsistent with other destination types that use slugs/IDs from the source object.

  • tests/test_agent_registry_store.py — Missing tests: (1) slug containing GLOB metacharacters, (2) multiple agents with same base slug (collision suffixes -00/-01), (3) empty slug, (4) status=None returning inactive agents (only tests active→inactive transition).

  • tests/test_routes_share_destinations.py — Missing tests: (1) inactive/suspended agent not appearing in destinations, (2) multiple agents with same slug — which one wins?
    VERDICT: Potential GLOB pattern injection in get_by_slug; missing test coverage for edge cases; seen deduplication uses slug not canonical_id.

  • tinyagentos/agent_registry_store.py:542slug is directly interpolated into a GLOB pattern via f-string without validation. If a caller passes a slug containing GLOB metacharacters (*, ?, [, ]), they can manipulate the pattern (e.g., slug="alpha*[0-9]" matches alpha-20260101-000000 AND alphax-20260101-000000). Current callers use system-controlled slugs, but the method is public and unguarded.

  • tinyagentos/agent_registry_store.py:542ORDER BY id LIMIT 1 returns the oldest row for a slug. If two agents share a base slug (same-second registration with collision suffixes -00, -01), only the first is ever returned. No test covers this collision scenario.

  • tinyagentos/routes/share.py:54seen.add(member) deduplicates by the slug (member), not agent["canonical_id"]. If two agents somehow share a slug (see above), the second is silently dropped. Should deduplicate by canonical_id for correctness.

  • tinyagentos/routes/share.py:57 — Destination id is the slug (member), not the canonical_id. Consumers expecting a stable canonical identifier get the slug instead. Inconsistent with other destination types that use slugs/IDs from the source object.

  • tests/test_agent_registry_store.py — Missing tests: (1) slug containing GLOB metacharacters, (2) multiple agents with same base slug (collision suffixes -00/-01), (3) empty slug, (4) status=None returning inactive agents (only tests active→inactive transition).

  • tests/test_routes_share_destinations.py — Missing tests: (1) inactive/suspended agent not appearing in destinations, (2) multiple agents with same slug — which one wins?

Automated first-pass review by the nemotron-ultra-kilo lane. The lead still reviews before merge.

@jaylfc

jaylfc commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Completed the supersede myself for the same reason: cherry-ported the CSRF dep from exec/tsk-f4nt53 and wrote the CORRECTED share-destinations docs section (require_device only, discovery-only, registry-backed coverage). 136 route+store tests green locally. Merges on green CI, then 2202 closes.

… gate get_by_slug input to slug shape (kills GLOB metachar surface)
@jaylfc

jaylfc commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Second fold round, both qodo/kilo Action-required findings adjudicated REAL and fixed: (1) a multi-user channel member that is a human username no longer falls through to slug resolution (auth.find_user guard - a username equal to an agent slug previously listed that agent as a share destination); (2) get_by_slug now rejects any input that is not strictly slug-shaped before building the GLOB pattern, with metachar tests (alpha[a-z], alpha*, alpha?, empty, uppercase). 137 tests green locally. Known gap, stated plainly: no route-level test exercises the human-username scenario because the destinations test fixture has no auth store wired; the guard is getattr-safe either way. Card that as a test follow-up if wanted. Kilo check state is the rate-limited fake-red; merging on qodo/gitar + my review once CI re-greens.

try:
if auth.find_user(member) is not None:
continue
except Exception:

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: Broad except Exception around auth.find_user(member) could silently swallow errors

If find_user raises (e.g., DB error, misconfiguration), the exception is swallowed and the code falls through to get_by_slug, leaving the false-positive risk unmitigated. Narrow the caught exceptions to expected failures so genuine errors surface instead of being hidden.


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

@jaylfc
jaylfc merged commit 0c99f04 into dev Aug 2, 2026
18 of 19 checks passed
@jaylfc
jaylfc deleted the exec/tsk-rll2pj branch August 2, 2026 09:12
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