Do not open a public issue. Report security issues directly to the maintainer via GitHub Security Advisories or email.
| Version | Supported |
|---|---|
| main branch (latest) | Yes |
Ayu.Core is a pure shell bot framework designed to run inside an isolated Docker container (busybox:musl). It has no external dependencies beyond the shell runtime and a single git submodule (hush-json).
- Never commit secrets. All tokens, passwords, and API keys must go through environment variables or the gitignored production overlay
etc/config.prod.sh(sourced whenAYU_ENV=prod; start frometc/config.prod.example.sh). The repo'setc/config.shholds empty defaults only. - The git history has been cleaned of leaked credentials. Before committing, always check
git difffor sensitive patterns. - Production secrets on the NAS are deployed via separate scp, never committed.
- Set
WEBHOOK_SECRETinetc/config.shto require?token=<secret>on all incoming webhook URLs. - The router returns real HTTP status codes:
403for a missing/wrong token,413for an oversized body,503for configuration/upstream failure,401for a bad QQ Bot Ed25519 signature,200on success. Returning 200 with a 403 body was an early bug — platforms treat 200 as delivered and stop retrying. /qqbotusesQQBOT_WEBHOOK_TOKENwhen set (URL-safe alphabet[A-Za-z0-9_-]), falling back toWEBHOOK_SECRET. Use the dedicated token whenWEBHOOK_SECRETcontains#,<or;. Even when the secret is missing,/qqbotnever falls back to an unauthenticated mode: Ed25519 signature verification still applies on every event.- Bot/API/webhook credentials are masked at the logging layer (
_log_scrubinlib/log.sh):token=,secret=,Bot <token>,Bearer <token>andapi.telegram.org/file/bot<token>are replaced with***before anything reachesvar/log. Existing credentials therefore never need to be rotated because of log exposure. - The dashboard API (
cgi-bin/api.sh) is read-only and reusesWEBHOOK_SECRET; it exposes credential flags (true/false) and a masked token, never the secrets themselves. - Special characters in tokens (
#,<,;, etc.) are handled via URL encoding.
Every callback to /cgi-bin/router.sh/qqbot is verified before it is parsed:
| Header | Meaning |
|---|---|
X-Signature-Ed25519 |
Ed25519 signature (hex) over timestamp + body |
X-Signature-Timestamp |
Unix seconds, part of the signed payload |
- The signing key is derived deterministically from
QQBOT_APP_SECRET(seed) via the hush-ed25519 submodule — no key files, no rotation. op=13(URL validation) is answered before event handling and returnsplain_token+signature.QQBOT_VERIFY_SIGselects the mode:auto(verify when a secret and the Ed25519 backend are both available),1(fail closed — unsigned callbacks are rejected),0(off, not recommended).- Signature failures return
401and are logged without the signature value or the secret. api.sh?action=confignever returns the secret:token_masked:"***", andTG_API_BASE(shapedhttps://host/bot<TOKEN>) is passed through_api_scrub_urlbefore serialisation.
- Outbound HTTPS goes through busybox
ssl_client(TLS 1.2 only). - TLS certificate validation is NOT performed — trust is based on network-layer isolation.
- Production deployments use Cloudflare Worker as a reverse proxy, with
X-Ayu-Tokenheader authentication at the edge. - All containers run in a Docker bridge network. Internal services communicate via
host.docker.internal, not127.0.0.1.
- The only external dependencies are two git submodules under the same maintainer: hush-json (JSON) and hush-ed25519 (Ed25519 sign/verify in shell + busybox awk). Both are pinned by commit (
160000); do not vendor them into the main tree. - No npm, pip, or other package managers are involved.
- The runtime image (
busybox:musl) is pinned to a specific version.
- Ayu.Core runs as pid 1 inside the container with no elevated privileges.
- The routing table under
var/state/msg-map{,-rev}holds message ids only (never message bodies or media). It is trimmed daily — entries older thanMAINT_STATE_KEEP_DAYS(30) and anything beyondMAINT_STATE_KEEP_ITEMS(5000) per conversation are removed;var/state/dc-cursoris never touched. Retention is configurable, andMAINT_STATE_DRY=1turns the trim into a report-only pass. - Code volume is mounted read-write (
/vol1/1000/Ayu:/ayu): the bot must persistvar/statemessage maps,var/loglogs and reloaded plugins. Shared image storage is mounted read-write as well (/vol1/1000/Lagrange/img:/tmp/img). Neither mount is read-only, so treat the host directory as state-bearing. - The container has no access to the host filesystem beyond the explicitly mounted volumes.
请勿公开发布 issue。 直接通过 GitHub Security Advisories 或邮件向维护者报告安全问题。
| 版本 | 支持 |
|---|---|
| main 分支(最新) | 是 |
Ayu.Core 是一个纯 shell bot 框架,运行在隔离的 Docker 容器(busybox:musl)中。除 shell 运行时和单个 git submodule(hush-json)外,无外部依赖。
- 绝不提交密钥。 所有 token、密码、API key 必须通过环境变量或 gitignored 的生产覆盖文件
etc/config.prod.sh传入(AYU_ENV=prod时被 source,从etc/config.prod.example.sh起步)。仓库里的etc/config.sh只有空默认值。 - git 历史已清理泄露凭据。提交前务必
git diff检查敏感信息。 - 生产环境密钥通过独立的 scp 部署到 NAS,绝不提交。
- 在
etc/config.sh中设置WEBHOOK_SECRET,所有 webhook URL 必须携带?token=<secret>。 - 日志层统一脱敏(
lib/log.sh的_log_scrub):token=、secret=、Bot <token>、Bearer <token>、api.telegram.org/file/bot<token>在写入var/log前一律替换为***,因此历史日志泄露不构成轮换密钥的必要理由。 - 面板 API(
cgi-bin/api.sh)只读,复用WEBHOOK_SECRET鉴权,只暴露密钥「是否已配置」的布尔值与掩码值,不返回明文。 - 状态码如实返回:token 缺失/错误
403,body 超限413,配置或上游异常503,QQ Bot 签名错误401,成功200。早期版本用 200 + 正文 403,平台会当作投递成功而不再重试,已修正。 /qqbot优先用QQBOT_WEBHOOK_TOKEN(限定[A-Za-z0-9_-]),回退WEBHOOK_SECRET;后者含#<;时会被平台截断,必须用专用 token。- token 中的特殊字符(
#、<、;等)通过 URL 编码处理。
/cgi-bin/router.sh/qqbot 的每个回调都在解析前先验签:
| 头部 | 含义 |
|---|---|
X-Signature-Ed25519 |
对 timestamp + body 的 Ed25519 签名(hex) |
X-Signature-Timestamp |
Unix 秒,参与签名 |
- 签名密钥由
QQBOT_APP_SECRET作为种子确定性派生(hush-ed25519 子模块),无密钥文件、无需轮换。 op=13(URL 校验)在事件处理之前应答,回plain_token+signature。QQBOT_VERIFY_SIG三态:auto(有 secret 且有 Ed25519 后端时自动开启)、1(失败即拒,未签名的回调一律拒收)、0(关闭,不推荐)。- 验签失败返回
401,日志不记录签名值与 secret。 api.sh?action=config不返回 secret:token_masked:"***";TG_API_BASE(形如https://host/bot<TOKEN>)在序列化前过_api_scrub_url打码。
- 出站 HTTPS 通过 busybox
ssl_client(TLS 1.2)。 - 不执行 TLS 证书验证 —— 信任基于网络层隔离。
- 生产部署使用 Cloudflare Worker 作为反向代理,边缘层通过
X-Ayu-Token认证。 - 所有容器运行在 Docker bridge 网络中,内部服务通过
host.docker.internal互访,而非127.0.0.1。
- 唯一外部依赖为两个同维护者的 git submodule:hush-json(JSON)与 hush-ed25519(shell + busybox awk 的 Ed25519 签名/验签)。两者都以 commit 固定(
160000),不要把内容平铺进主仓。 - 无 npm、pip 等包管理器。
- 运行时镜像
busybox:musl锁定特定版本。
- Ayu.Core 作为容器内 pid 1 运行,无特权提升。
var/state/msg-map{,-rev}路由表只存消息 ID 对照(不含消息正文与媒体),每日修剪:超过MAINT_STATE_KEEP_DAYS(30 天)以及单会话超过MAINT_STATE_KEEP_ITEMS(5000 条)的条目会被删除,var/state/dc-cursor永不触碰。保留窗口可配置,MAINT_STATE_DRY=1时只统计不删除。- 代码卷为读写挂载(
/vol1/1000/Ayu:/ayu):需要写var/state消息映射、var/log日志及热更新插件;图片存储卷同样读写(/vol1/1000/Lagrange/img:/tmp/img)。两个挂载都不是只读,请按有状态目录对待。 - 容器仅能访问显式挂载的卷,无法访问宿主文件系统。