Security and known limits¶
An honest list. The first half is how the platform is built to behave; the second is what the repository itself names as not verified, not finished, or not done. Everything here is taken from the specification, the kit specifications and the development notes.
Where it is meant to run¶
AHEGAO Kit is designed for an internal network. The compose file
publishes the core on plain HTTP on port 8090; nothing in the repository
terminates TLS. AG_SECURE_COOKIES=1 exists for the case where you put the
platform behind HTTPS yourself — set it only then, or the browser will not
return the session cookie. Some interface features depend on this too: the
clipboard import/export of backup targets falls back to a copy-paste window
because navigator.clipboard needs a secure context, "and a platform reachable
over plain HTTP in a closed network does not have one — and that is most
installations".
The Network Map Kit sits on the host network with NET_RAW (no
privileged mode), so its port 8101 is on the LAN it scans. PostgreSQL is
published on 127.0.0.1:5432 only — "the database of a tool that holds
credentials has no business being reachable from the network it inventories".
Three entrances that do not cross¶
/api/...— the browser session;/capi/v1/...— a kit, with its own service token;/export/v1/...— an external system (Zabbix, Prometheus) with a read-only token, separate from any user account and scoped.
None of the three credentials works on another's paths. Through the browser
proxy (/m/<kit>/...) only the kit's browser surface is reachable: api/...
and interface files. Endpoints that trust the core's own token —
/manifest, /metrics, /health, /jobs/result — are not proxied; this
was fixed after the least-privileged role could otherwise post a fabricated
"restore verified" result into a kit.
Kits trust the core through a shared secret¶
Between the core and a kit there is a token derived from AG_MODULE_SECRET
(security.derive_token): both sides compute it, nothing stores it. A kit
does not trust a request without it. This is why the secret is required:
without it a kit — and the network kit's port is on the LAN — would trust
whoever called it first.
Required literally: neither the core nor a kit starts without it, and the
refusal says how to generate one. The compose file in this repository has
always refused too (${AG_MODULE_SECRET:?…}), so the gap was for anybody
deploying the images another way — another orchestrator, a hand-written docker
run — where the old default was a warning in the log and trust in the first
caller. An explicit AG_INSECURE_TRUST_FIRST_CALLER=yes restores the old
behaviour for a machine where nothing but the core can reach the kits; the name
is deliberately ugly, because nobody sets a variable with insecure in it by
accident.
Signing in costs something to guess¶
Ten failed sign-ins per login in a quarter of an hour, and a hundred from one
address in the same window, after which the answer is 429 with a
Retry-After — counted in the database rather than in memory, because the core
can be run with more than one worker and a limit each worker counts on its own
is a limit divided by the number of workers.
It is deliberately not a lockout. The window slides, a correct password
clears the count, and an attempt that is already being throttled does not
extend the wait — otherwise a stranger could keep somebody out of their own
account for as long as they kept typing at it. The two numbers differ for the
same reason: an office behind one NAT is one address for everybody in it, and a
limit that locks out a floor because somebody mistyped is a limit that gets
switched off. X-Forwarded-For is believed only when AG_TRUST_PROXY=1, since
a header anybody can set would otherwise let anybody be any address.
Rights, and the absence of rights¶
- Authentication is mandatory and cannot be switched off. Passwords are
stored as
scrypthashes only; API keys and agent keys as hashes. - Rights are granted per scope inside a kit (
user × organisation × kit × scope × role), with rolesviewer,editor,admin. - Absence of rights means invisibility. A user must not learn that an
object they are not allowed to see exists — not from lists, not from error
messages, not from response timing. Denial and absence answer the same way:
404, never403. - Actions on users, rights, the licence, storages and agents are written to the journal: who, what, when. The journal survives deletion of what it is about.
Secrets¶
- Encrypted at rest with
AG_SECRET_KEY(AES-GCM, with the organisation and the secret's name as associated data, so a ciphertext copied into another row does not open). Without the key the core does not start. - A secret is never returned by the API, not even to an administrator.
Only write and replace. "The endpoint that returns the value does not
exist" — not restricted, non-existent, neither in
/apinor in export. - An executor receives a secret inside its job, for one use; an agent does not write it to disk. The journal records the fact of use, not the value.
- The installation key is shown by its fingerprint, and a secret that the current key cannot open is shown as such.
- A secret can be a pointer into HashiCorp Vault instead: the value is read at the moment of use, never cached; the path can be checked without revealing the value (the answer is the list of field names at that path). The platform's own Vault credentials come from the environment or from files, never from its database.
- Webhook URLs of alert channels are treated as secrets: only the host is shown, and only an administrator can read the channel list.
- A kit that connects over SSH creates its key on request; the private half goes into the platform's secret store and is returned by nothing. Tokens never appear in URLs, process lists or child environments.
- Host keys are pinned. There is no "do not verify" option anywhere in the product: a kit either refuses a first connection to an unknown host until a person confirms the fingerprint, or learns the host key on first contact and then requires it; FTPS, S3 and registries accept a self-signed certificate only by a pinned fingerprint.
Data¶
- Every schema is versioned by its own migrations; a released migration is never edited — and this is checked rather than trusted: each applied migration is recorded with a checksum of its text, and a service refuses to start against a history it cannot account for. A database newer than the build, a gap in the applied versions, or an edited released migration are three separate refusals, each naming what it found. See Upgrading.
- Data entered by a person is untouchable — it survives any rescan, reclassification and version upgrade.
- The last verified-restorable copy is never deleted, whatever the retention policy says; a storage with copies in it cannot be deleted.
- A storage carries a marker (
.ag-storage). A path without its marker is not that storage — a refusal before the first byte, not a "copies went missing" alarm. This was reproduced on a test installation: a mount that fell away looked like 112 GB free and a successful copy onto the system disk.
What is scanned, and what is not¶
Every push runs Trivy over the source tree: third-party dependencies, secrets committed by mistake, misconfigured Dockerfiles. Every release tag additionally builds each image the release ships and scans the image itself — what is actually installed, which is not always what the requirements file says. The pipeline fails on HIGH and CRITICAL findings that have a fix upstream.
Two things the gate deliberately does not do:
- It does not fail on vulnerabilities nobody has fixed. A base image can always carry some; a gate that is red every day is a gate that gets switched off. They stay in the full report, which the pipeline keeps as an artifact. The images are Alpine-based, which keeps that tail short.
- It does not hide accepted findings. What is accepted on purpose is
listed in
.trivyignorein the repository, each entry with its reason. At the time of writing the list has one entry: the images run as root, for the reasons written beside it.
The images carry no pip: the installer is removed once the dependencies
are in, so the picture the scanner sees is the picture at run time.
Known limits¶
Named in the repository so that they do not look verified.
In progress¶
Not done¶
- Re-encryption of secrets and copies when
AG_SECRET_KEYchanges. The core shows that the old key no longer opens them; the procedure itself does not exist yet. - Dynamic Vault credentials (
database/creds/<role>): the value is already read at the moment of use, but lease renewal and revocation are not implemented, and the interface does not promise it. - Point-in-time recovery, physical backups (
pg_basebackup, WAL, XtraBackup,mariabackup, binlogs) — not in this release. Planned for the next development iteration of the database kits; until then the kits do logical dumps only, and say so in their verdicts. - IPv6, SNMP/LLDP and automatic L2 topology; the platform is not a general-purpose monitoring system and not an orchestrator.
- The verifying public key for licences is baked into a release build
(
config.RELEASE_PUBKEY); in the source as published it is empty, and the environment overrideAG_LICENSE_PUBKEYis not a security boundary.
Not verified automatically¶
From the development notes, section "What is not checked automatically":
- the map under the mouse, and layout; the dark and light themes by eye;
- delivery to a real Telegram: the suite checks a webhook into nowhere — the path and the recorded refusal, not the third-party API;
- the agent channel with a real agent: the suite imitates an agent with
curl; the scanning/script agent has not been ported to the platform yet.; - whether an application works on a restored database: the kit proves the dump restored and matches what was recorded at dump time, and says in its verdict that it checked no more;
- restore into production under load — the suite restores into a separate database on a test installation, which is a smaller claim;
- load and large volumes: the network was checked on one
/24(76 hosts), backups on a 16 MB database; - password-based SSH connections: the path exists, only keys were tested;
- migration between PostgreSQL versions.
Limits of the method¶
- Logical dumps give no point-in-time recovery; on very large databases the dump window may be unacceptable — that is the case for physical backups, which the kits do not do.
- Restore verification needs space and time comparable to the database itself; on large targets it is realistic less often than the backup.
- Row-count comparison (verification level 3) does not catch corruption inside rows.
- Nexus OSS: deletion in a registry is irreversible; an error in the middle of a clean-up is named, not rolled back, and the next run continues from where it stopped.