Report privately through GitHub Security Advisories. Do not open a public issue for an undisclosed vulnerability.
Include the image tag or sha-<commit>, the RouterOS version and
architecture, reproduction steps and the impact. The first response comes
within a few business days, and fixes land on main and ship in the next
promoted image (see Tags).
A vulnerability in T3 Code itself belongs upstream at pingdotgg/t3code. Report one in a provider CLI (Claude Code, Codex, OpenCode and others) to its vendor. If it affects this image in a particular way, tell us as well.
app-t3code follows T3 Code's security model and adds no access-control
layer of its own. T3 decides who can connect and what a paired client can
do; this repo packages T3 for RouterOS /app and documents what that
packaging exposes. If T3 logs something, the container logs it too.
The trust boundaries, from the outside in:
- RouterOS access implies container access. A RouterOS user with write
access to
/containeror/appcan run commands in the container (/container/shell, asnodeby default and as root withuser=root), mint a pairing token (t3app pairing), change its environment, or replace its image. Treat router admin accounts as admin accounts for T3 and everything in/data. - Treat a paired T3 client as a full T3 admin. The startup pairing token grants T3's administrative scopes, including terminals, file writes and provider management. Anyone paired with a terminal can run any command the container user can.
- Agents run as the container user. T3, its agents and its terminals run
as
node(uid 1000) with nosudo. They can read everything in/data, including provider logins,ghtokens and API keys passed in the environment. The container is notprivilegedby default. Which Linux capabilities and devices a RouterOS container gets is still being measured (#7). - The network listener is T3's. T3 serves HTTP on port 3773, and the
/appYAML maps it through RouterOS. Useuse-https=yes(RouterOS/ip/cloudor your own domain) for anything beyond a lab.use-https=nois for CHR labs, where there is no/ip/cloud.
On every start, T3 prints a one-time pairing token, a pairing URL and a QR
code to the container's output. RouterOS keeps that output in
/container/log (WinBox Container → Log) whether or not the container
has logging=yes. Until it is used or expires, about 5 minutes after the
start, that token pairs a browser as a full T3 admin.
Measured on RouterOS 7.25: a user in the built-in read group can see
the token through /container/log (REST GET /rest/container/log), but
cannot use /container/shell. So for about 5 minutes after each start, a
read-only RouterOS account can gain control of T3, and through it a shell in
the container.
This image does not suppress or filter the banner. T3 has no option to turn
it off, and filtering T3's still-changing output would be fragile. Until that
changes, treat read-group access to a router running this app as access to
T3 for the first 5 minutes after every start. Every start prints a new
token: a router reboot, /app/update, or toggling the app. RouterOS user
group policies are per protocol, not per menu, and skins hide menus only in
WebFig and WinBox, not over the API or REST. So no group setting hides
/container/log from a read user. MikroTik's own
User
page already warns against giving the built-in read group to untrusted
users (it includes reboot and sensitive).
The supported way to pair is still t3app pairing (see
Pairing with the web UI), which needs
/container/shell, so it needs write access.
The image installs some software into /data after it starts, so its
version is not fixed by the image:
| What | Source | Check |
|---|---|---|
Claude Code (T3APP_PROVIDERS) |
Anthropic's release manifest, stable channel | size and SHA-256 from the manifest; the manifest's signature is not yet checked (#13) |
Codex, OpenCode, Pi (T3APP_PROVIDERS) |
npm registry, latest | npm's registry integrity; not pinned, by design, so T3's Update now can manage them |
Grok, Muse (T3APP_PROVIDERS) |
the vendor's install.sh over HTTPS |
whatever the vendor's installer checks |
dev tool group |
npm, from a committed lockfile | lockfile integrity hashes |
capture, labs, vpn, routing tool groups |
Debian packages pinned at image build | size and SHA-256 from apt's signed indexes |
Provider CLIs update through T3 or their own updaters after install, the
same as on any T3 host. T3APP_PROVIDERS=none and
T3APP_TOOL_GROUPS=none stop this image's own runtime installs. They do
not stop T3 itself from downloading things: its built-in browser on first
use, its managed providers (Codex's Continue with ChatGPT, Antigravity)
and any Install you click on its welcome screen.
RouterOS starts containers with umask 0000. Since the 2026-11-09 image the
entrypoint sets umask 022 before starting T3, so new files in /data are no
longer world-writable. Files created by older images keep their modes. The
home belongs to node (uid 1000), the user T3's terminals run as, so
chmod -R go-w /data from a T3 terminal fixes them. Afterwards,
find /data -xdev -perm -0002 ! -type l should print nothing; anything it
still lists belongs to another user, and only that user can change it.
Commands run directly
through RouterOS /container/shell bypass the entrypoint and still use
0000, except t3app, which sets 022 itself, so tool payloads and provider
CLIs it installs in the background aren't world-writable. Payload files
installed from /container/shell by earlier images may be; the
chmod -R go-w /data above fixes them too.
The image sets T3CODE_TELEMETRY_ENABLED=false. Set it to true to opt back
in to T3's telemetry. Provider CLIs have their own telemetry settings.
- Images are built by GitHub Actions and published to
ghcr.io/tikoci/app-t3code. CI pushes:sha-<commit>candidates frommainand from manualworkflow_dispatchruns on any branch, so asha-tag alone does not mean the commit is onmain. Only amainrun moves the release tags (:<version>,:0.1,:0,:latest) and adds the build's own:<version>-app.<N>, and only after the arm64 container-replacement test passes. - Build-time downloads are pinned: T3 Code by version,
ghand the CI linters (hadolint, ShellCheck, actionlint) by version and SHA-256, anduvby image tag. - Workflow actions are pinned to a commit SHA, with the release in a comment. Dependabot proposes updates monthly (GitHub Actions only).
- A ruleset on
mainblocks force-pushes and deletion, requires a pull request with every review thread resolved, and requires CI'slint-testandbuildjobs.buildskips changes that can't reach the image (docs,.agents/,.claude/), and GitHub counts a skipped job as passing, so the image build gates only image changes. Repository admins can bypass it, for small doc fixes straight tomain. - Daily freshness checks (
.github/workflows/freshness.yml) flag a new upstream T3 release, a change in Claude Code's download contract, and a newer release of any other pinned download (gh, uv, Bun, the CI linters). While they fail, afreshnessissue stays open with the report.
The Security tab is the live source of alerts and advisories. What runs today:
- CodeQL: GitHub default setup,
defaultquery suite, forjavascript-typescriptandactions. - Dependabot: alerts and security-update PRs, enabled. Security PRs on
lib/groups/dev/package-lock.jsonchange thedevpayload id, so every home re-downloads it on its next start, which is the intended security refresh. Version updates run for GitHub Actions only. - Secret scanning with push protection: enabled.
- Private vulnerability reporting: enabled.
| Version | Supported |
|---|---|
The current :0.1 (newest promoted build) |
✅ |
Older builds and sha-<commit> candidates |
❌ |
Update with /app/update (see Install). The image
version is the upstream T3 Code version; see CHANGELOG.md
for what each build changed.