nanocoai

Activity

Stars and mentions · Last 30 days, Oct 7 – Nov 5

GitHub stars203
Mentions4
05101520Oct 7Oct 21Nov 5
UTC days · repositories nanocoai owns2 repositories

Top repositories

nanocoai’s repositories ranked by pull request authors, as on trending.

Loading

Contributes to

Repositories outside nanocoai’s account where nanocoai opened or reviewed pull requests in the past 365 days.

Who noticed nanocoai

Everyone who starred or forked nanocoai’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

4posts
Nov 60 posts
  1. One phone number, two users: normalising WhatsApp sender IDs across Baileys and the Cloud API If a system can receive messages from the same person through two integrations, both must produce the same user ID, or that person silently becomes two users with different permissions. In NanoClaw, the Baileys WhatsApp integration identified a sender as 15551234567@s.whatsapp.net (a JID), while the WhatsApp Cloud API path used the bare wa_id, 15551234567. Roles and group memberships granted to one identity never applied to the other. My fix (#3070, in review) normalises the handle at one point and migrates existing data, merging the duplicates. Why did one number become two users? | Path | Sender handle | Stored user ID | | ------------------------------------------ | ---------------------------- | ------------------------------------- | | Baileys (native WhatsApp Web protocol) | 15551234567@s.whatsapp.net | whatsapp:15551234567@s.whatsapp.net | | WhatsApp Cloud API (via a chat SDK bridge) | 15551234567 | whatsapp:15551234567 | Both paths use the channel type whatsapp, so the two IDs became two rows in users. Anyone moving a number from Baileys to the Cloud API, or running both at once, found that the approvals, roles and agent-group memberships they'd set up "didn't work". In fact they'd been granted to a different user. The cause was one line: the code built the user ID directly from whatever raw handle the adapter emitted. How should you normalise identifiers? 1. Pick one canonical form. Here that's bare digits, which the Cloud API already emits. 2. Normalise at one point, as early as possible. A normalizeWhatsAppHandle(channelType, handle) helper strips the @s.whatsapp.net domain and the optional :device suffix, and it's applied exactly where the user ID is built. 3. Scope it narrowly. It only touches WhatsApp user JIDs. Group JIDs (@g.us) and every other channel pass through unchanged. 4. Don't normalise identifiers that mean something else. The messaging-group routing ID is left as the raw JID on purpose, because routing depends on it. "Who sent this" and "where to reply" are different identities. How do you fix data that's already split? Normalising new messages isn't enough: existing installations already have both rows. A one-time migration: - Rewrites every user ID stored in JID form to the canonical form. - Cascades the rename through every table that references users: roles, agent-group members, DMs, and the optional approval and dropped-message tables. - Merges when a canonical row already exists (because the person also messaged through the Cloud API). Roles and memberships are unioned into the canonical row and the JID row is deleted, so nobody loses access in the merge. - Runs in a transaction, with the migration runner checking foreign-key integrity before it commits. The general lesson Any system with more than one way in (two messaging providers, email plus OAuth, a web app plus an API) needs a canonical identity decided at the boundary. Otherwise permission bugs show up as "it works for some people", which is the hardest kind to debug. For agent platforms the stakes are higher: identity decides who may approve what an agent does. Related: localhost is not authentication, another NanoClaw fix. --- Written by Ali Ahmed · Original article

  2. Localhost is not authentication: fixing a CWE-306 webhook in an AI agent framework Binding a server to 127.0.0.1 keeps the internet out. It does not keep out the other programs on the same machine. In NanoClaw, an AI agent framework, a loopback webhook that relays chat-gateway events accepted any local POST, so any unprivileged process could forge button clicks into the agent's response pipeline. That's CWE-306, missing authentication for a critical function, rated CVSS 6.6. I wrote a fix (#3065). The maintainers then shipped a simpler fix (#4013) that credited it, and comparing the two is the most useful part of this story. What was the vulnerability? The issue (#2970) was reported by security researcher YLChen-007. NanoClaw's chat bridge started a small HTTP server on 127.0.0.1:<port>/webhook to receive events forwarded from the chat gateway (Discord, in this case). Every POST body went straight into the event handler, and the loopback address was the only trust boundary. So any local process could send a forged GATEWAY_INTERACTION_CREATE payload, and the agent would treat it as a real user clicking a button: approving an action, answering a question, choosing an option. For an agent that acts on those answers, that's a direct path to making it do things nobody approved. Why isn't localhost a trust boundary? On a shared machine, CI runner, container host or developer laptop, "local" includes browser helpers, other users' processes, compromised dependencies and malware with no special privileges. Any of them can open a TCP connection to 127.0.0.1. If an endpoint changes behaviour, it needs to know who is calling, not just from where. How did my fix work? I generated a 256-bit random secret with crypto.randomBytes(32) when the server starts, and put it in the path of the webhook URL handed to the gateway listener: http://127.0.0.1:<ephemeral-port>/webhook/<64-hex-char-secret> Every request was checked with crypto.timingSafeEqual before the body was read, and anything else got 401. My reasons: - The secret goes in the path, not a header, so the adapter interface doesn't change: it already accepts a webhook URL. - timingSafeEqual is defence in depth. Loopback timing attacks are impractical, but it costs nothing. - A length check comes first, because timingSafeEqual needs equal-length buffers. The length of the correct path isn't secret, so this leaks nothing. Four regression tests covered: a valid POST to the secret path, the original proof-of-concept path rejected, a wrong secret rejected, and a control payload that reaches the server but triggers no action. What did the maintainers ship instead? Their fix in #4013 makes each request carry an x-discord-gateway-token header equal to the bot token, compared in constant time. The adapter's listener already sends that header on every forward, so there's no new secret, no URL change, and no conflict with later changes to the bridge. A missing or wrong token gets 401 before the body is read, and if no bot token is configured, every request is rejected. Their PR superseded mine (which by then conflicted with the bridge) and credited it. Why was their fix better? Both close the hole. Theirs wins on things a reviewer cares about more than an outside contributor tends to: | Aspect | Secret in URL path (mine) | Existing token in header (shipped) | | --------------------------------------------------- | ------------------------- | ---------------------------------- | | New secret to manage | Yes, per process | No, reuses the bot token | | URL / interface change | URL changes | None | | Merge conflicts with ongoing work | Yes | No | | Fails closed when not configured | — | Yes: no token, no access | | Risk of the secret leaking via logs that print URLs | Some | Lower: headers are rarely logged | The lesson: before inventing a credential, look for one the system already sends. The best security fix is often the one with the smallest new surface. How should you secure local webhooks? - Authenticate every state-changing local endpoint. Use a shared secret, a token the caller already holds, or a Unix domain socket with file permissions. - Check before reading the body, and compare in constant time. - Fail closed: no secret configured means no access, not open access. - Prefer credentials the caller already has over new ones. - Treat inputs that drive an agent's decisions as security-critical. A forged "approve" button is a privilege escalation. I've also written about building an OAuth 2.1 MCP server, another place where AI agents meet authentication. --- Written by Ali Ahmed · Original article

  3. Your AI agent thinks its tasks were deleted: how to write tool errors for LLM agents A human reads "not found" and goes looking. An AI agent reads "not found", concludes the thing is gone, and acts on that conclusion: it recreates it, deletes something else, or tells the user their data was lost. In NanoClaw, an agent framework, a bare "task not found" across sessions led agents to decide their scheduled tasks had been deleted, so they created duplicates. The fix I proposed (#3068, in review) changed no logic. It made the errors and outputs tell the agent where the task actually is and what to do next. What went wrong? In NanoClaw, scheduled tasks are scoped to the agent group, not the session. A group can have several sessions (a broadcast channel and an operator console, for example), and a task created in one is visible from the others. But the task tools gave poor feedback across sessions. When an agent in one session called get_task or update_task on a task that lived in a sibling session (or had completed or been cancelled), the answer amounted to "not found". The agent's mental model, "tasks are mine and per-session", filled in the rest: my task was deleted. So it scheduled it again, which produced duplicated or misconfigured schedules (issue #2992). How do you fix an agent's wrong mental model? Three changes, none of which touch the scheduler itself: 1. Errors that explain. When a task isn't found in the current session, the tool now searches the other sessions in the group. If the task is there, the error names the session and messaging group it lives in and its status (such as completed or cancelled), and tells the agent to look it up with get <id> instead of assuming it was deleted. 2. Context in normal output. list_tasks and get_task now include the task's origin messaging group (id and name), so the agent can tell which task belongs to which setup without guessing. 3. Instructions that match the implementation. The scheduling instructions the agent reads now say plainly that task visibility is group-scoped, and explain cross-session listing. A regression test reproduces the original cross-session scenario from #2992. What makes a good tool error for an LLM? I've applied the same rules in the read-only MCP server I built for a CRM: - Say why, not just what. "deals are not visible to your role" stops a retry loop. "403" starts one. - Say what to do next. "The result is too large: use a smaller limit or more filters" gives the model a concrete next move. - Never leave a believable but wrong conclusion open. If "not found" could mean deleted, elsewhere, or never existed, say which one. - Separate tool failures from protocol failures. In MCP, a tool that ran and failed returns content with isError: true, which the model reads. A malformed request is a JSON-RPC error. Mixing them up hides the useful message. - Don't leak internals. "The query failed" is better than echoing database error text into a model's context, and into its answer to the user. - Hide what the user can't use. Tools a role can't call shouldn't appear in tools/list at all. Why does this matter so much for agents? With a human user, a vague error costs a support ticket. With an agent it costs an action: a duplicate schedule, a second email, a recreated record. Agents act on the most likely reading of what they see. The tool's output is effectively part of the prompt, so write it like part of the prompt. More AI engineering write-ups: how to stop an LLM from inventing numbers and the context-compression bug that made agents loop. --- Written by Ali Ahmed · Original article

  4. YouTube · · YouTube description · githubrepo_cloud · nanocoai/nanoclaw

    ⚡ [Top 5 AI Repos This Week] nanoclaw: The Free Tool Taking Over GitHub! #shorts Discover nanocoai/nanoclaw: A lightweight alternative to OpenClaw that runs in containers for security. Connects to WhatsApp, Telegram, Slack, Discord, Gmail and other messaging apps,, has memory, scheduled jobs, and runs directly on Anthropic's Agents SDK 💬 Comment "NANOCLAW" below for the direct link, or grab it from the links below: 👉 Interactive Star History & Live Analysis: githubrepo.cloud ⭐ Embed Live Verified Badges in your README: githubrepo.cloud 🔗 Direct GitHub Repository: nanocoai/nanoclaw 📊 Repository Specs: ⭐ Stars: 30,944 🍴 Forks: 12,765 💻 Language: TypeScript 📜 License: MIT #shorts #nanoclaw #aiagents #aiassistant #claudecode #claudeskills #ai #artificialintelligence #machinelearning

    2 likes · 75 views

Achievements

Awards earned by the repositories nanocoai owns. Select an award to embed its badge.

Gold
01st place finishes
Silver
22nd place finishes
Bronze
13rd place finishes
Awarded repositories
1Owned by nanocoai now

Repositories

Ordered by gold, then silver, then bronze.

  1. nanoclawBest: 2nd by stars, 749 stars021

Every award

Top-three finishes of these repositories in completed trending days and weeks, newest first.

  1. nanoclaw
  2. nanoclaw
  3. nanoclaw

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.