Security Posture · Confidential

Amadan Security Posture

Compromise the server and you still get nothing — no repo contents, no file index, no usable keys.
Prepared for Cillian Updated 27 Aug 2026 Baseline 662bffb Gate make test green (6/6) See the fix changelog →
14
Findings fixed & test-covered
11
Hardening items recommended
0
Live paths to server-side plaintext
Aikido SAST + secrets: clean
100%
make test gate green

Findings by status

14 fixed 11 open
Fixed this cycle, each with a regression test Recommended hardening (design / infra / upstream)

Six reviewers each attacked one part of Amadan — auth, access control, transport, CI, crypto, and the web layer — and traced every issue to the real code and the test that catches it. An Aikido SAST + secrets scan ran alongside.

The result that matters: nobody found a way for a hacked hub or instance to read an E2EE repo's contents, index, or keys. The bugs that were actually exploitable lived in the ordinary app and infrastructure layers, and they're fixed. What's left is hardening.

Findings at a glance

Every finding and where it stands. Full write-ups of the fixes are in the changelog.

IDFindingSevSurfaceStatus

What the server can see

Amadan's promise: hack the server, get nothing. For E2EE repos that holds for content. Here's exactly what metadata the server can still see, read straight from the code.

Verified

An E2EE repo is only ever ciphertext on the server: code, file names, branches, commit messages, the index, discussions, CI logs and secrets. The server never writes a plaintext copy to disk. Every git and browse route returns 404 first. A canary push leaves no trace in the raw database, and the stored packs aren't valid git — you can't unpack them without the key.

What it can still see on an E2EE repo

CT ciphertext · PT plaintext on server · meta structural metadata · H keyed handle (correlatable, not the name).

Dataprivate_e2eeNotes
Git contents, file names, tree shape, commit messages/authors/datesCTSealed packs; never materialized
Refs, branch names, default branch, commit SHAsCTInside the sealed manifest
Browse index, README, discussions, CI logs, CI secret values & namesCTPer-purpose sealed subkeys
Manifest version, pack count, sealed pack sizes, push timingmetaActivity, not content
Each member's public keys and role; member countPTThe members table (wrapped key stays CT)
Who pushed / posted / reported CI, and whenmetaAuthor pubkey + timestamps
Repo existence, namespace, name, tier, rename historymetaHub metadata by design
Branch / commit / secret / pipeline identifiersHKeyed handles — counts, not names

One caveat. The plain private tier is GitHub-style: content and secrets sit in plaintext on the server, protected by permissions rather than crypto. Only private_e2ee gets the "server sees nothing" guarantee. Worth saying out loud so nobody over-claims it.

Still open

Hardening worth doing, none of it urgent. Start with R-01 — it's the only open item that touches the core promise.

What held up

Things the reviewers tried to break and couldn't, with the tests that keep them that way.

  • Sessions & CSRF. Random session IDs, stored hashed, with a server-side expiry. Cookies are HttpOnly and Secure. Every form that changes state carries a CSRF token, checked in constant time.
  • OAuth. Random state, PKCE, redirect locked to the hub's origin. No open redirect.
  • Stored secrets. Tokens and login codes are hashed and looked up by hash, so there's no timing leak. Internal tokens are encrypted. Nothing sensitive hits the logs.
  • Instance isolation. Repo servers listen on localhost only. The hub strips auth headers before proxying. A client can't reach an instance directly, and the git token never crosses to it.
  • Git commands. Built as argument lists, never a shell string. SHAs, refs and paths are validated. Merges run with a stripped environment, so repo config can't trigger hooks.
  • Web output. No innerHTML or eval anywhere in the 49 JS files. Server templates auto-escape. The one raw-HTML path is an escape-only markdown renderer. CSP and anti-framing headers are set globally.
  • CI. A runner can't report a passing build for a commit it didn't run. Job claims are race-safe. Reading secrets needs a lower role than writing them, and the hub sets the role, not the runner.
  • Crypto. Standard ECDH, HKDF and AES-GCM, a fresh IV each time, a separate key per purpose. The browser and Go code produce identical output against pinned test vectors.

Aikido scan

A local Aikido SAST + secrets scan of the changed and security-critical code, run on the machine.

Result — clean

No secrets. No real bugs. Two heuristic flags came up; both are false positives once you read the code.

RuleWhereWhy it firedAssessment
G204
command exec
runnerisolate.goRuns a subprocess whose path comes from a variable.False positive — the path is exec.LookPath("bwrap"), the args are fixed flags, and there's no shell. Nothing to inject.
G107
SSRF
oauth.goBuilds a request URL from a config value.False positive — the host is AMADAN_KEYMAIL_ISSUER, set by whoever runs the deployment. A user can't steer it.

Can Aikido gate merges in Amadan?

Yes — as a CI step, with no changes to Amadan. It slots into the merge gate Amadan already has. A built-in integration isn't possible, because Aikido doesn't connect to Amadan as a git host.

How it fits

Amadan already blocks a merge unless the latest commit passed CI. Add Aikido as a step in that CI, and a failed scan blocks the merge. Two ways to run it:

  • Local Scanner (Docker or a binary), run in a job with --gating-mode pr --fail-on critical. It exits non-zero when it finds new issues, which fails the build. The code stays on the box — the best fit for E2EE.
  • CI API client — lighter, but it wants a git-provider ID that Amadan doesn't have. Use the local scanner.

What you need

  • A CI secret holding your AIKIDO_API_KEY.
  • A pipeline step that runs the scanner — use the binary on the arm64 box, which has no Docker.
  • The repo's require-green switch turned on.

A built-in status check, like Aikido posts on GitHub PRs, would need Aikido to support Amadan as a git host, or Amadan to expose a compatible API. Neither exists yet.

Fix Changelog

What we fixed

Every fix from this round, each with its own test, all green under make test. Line references are on branch security-posture. ← Back to the dashboard