See every MCP server and coding agent on the machine, including the ones nobody approved. Metadata-only inventory. Self-hosted. Personal mode makes zero outbound calls.
pipx install aimonitoring-security && aim personal
# → http://127.0.0.1:8787Community is free. Self-host it or run aim personal. Uncapped seats,
uncapped agents, Apache-2.0, no DRM, no phone-home. Personal mode makes
zero outbound calls. Cloud: 3 agents free, then $2 per monitored agent /
month. Enterprise: contact sales. Tariff: docs/pricing.md
(HTML).
This repo is the public Community clone. It ships endpoint collectors, ingest +
Postgres, a guardrail engine, identity-sync, shadow-AI discovery, Gatehouse
(free PR-security, not a CI/CD product), hygiene, an alert bus, the analyst
dashboard (apps/web + apps/api), the aim CLI, and Helm / Intune / Linux
install paths. Full map: docs/architecture.md.
Docs: https://hawikk.github.io/aim/ · documentation index · stack overview · Gatehouse · pricing · changelog.
This repository is a curated public snapshot of a larger working repo. It carries the code and the documentation you need to run and extend the product; design notes, rollout records, and deployment-specific compliance material are not published here, so a few cross-references in the docs will not resolve.
Two consequences worth stating plainly, because the docs below reference both:
only ci.yml ships here: the release, attestation and matrix workflows
live outside this snapshot, so any doc pointing at
.github/workflows/release-*.yml is describing something you cannot run, and
no container images are published, so the GHCR pull path in
docs/deployment/prebuilt-images.md will
not resolve. Neither blocks you: install-pilot.sh defaults to prefer-pull
and falls back to building from source, which is the supported path here. The
one artifact that is published is the CLI wheel on PyPI
(aimonitoring-security).
| Tier | Price | How you start |
|---|---|---|
| Community | Free, uncapped | Clone this repo or pipx install aimonitoring-security && aim personal |
| Cloud | 3 agents free, then $2 / agent / month | Request Cloud access |
| Enterprise | Contact sales | sales@getaimonitoring.com |
Community is Apache-2.0: no DRM, no phone-home, no seat file. Cloud is a hosted dashboard we operate (copy only: no self-serve checkout yet). SSO, enforcement packs, evidence, DPA, and air-gap are Enterprise.
Need the full dashboard with Docker (about 30 minutes)?
git clone https://github.com/hawikk/aim.git
cd aim
./scripts/demo-stack-up.sh
# → http://127.0.0.1:8081 (personal/standalone local admin; no SSO)Most engineering orgs have no central view of which AI coding tools are in
use, what they cost, or whether secrets / personal data are flowing into them.
AIM closes that with a metadata-only platform: collectors report
pseudonymized usage metadata; the dashboard turns it into fleet visibility and
security findings, without storing prompt text, code, or raw identities.
Platform findings are observe/alert by default. Enforcement is deliberately
narrow and only where a vendor hook exists: Claude Code, Cursor, Kimi
Code, and GitHub Copilot (VS Code) can block on the endpoint when a
managed enforcement.json is loaded. Grok Build denies tool calls
only. Kilo Code stays observe-only (see Trust, and
collectors/parity-matrix.json).
Screenshots below are live captures of the analyst console at 2880×1800 @2×: Home, Security, Live activity, Fleet, plus light theme. Source: a running personal/standalone stack with synthetic data. Regenerate anytime with:
# apps/web has Playwright; demo dashboard is :8081 (or set AIM_BASE / --base)
cd apps/web && npx playwright install chromium # once
node scripts/capture-readme-screenshots.mjs \
--base http://127.0.0.1:8081 \
--out ../../docs/screenshotsHome: unattributed rate, open criticals, active users, events in range, live activity chart, and identity-coverage health.
Security: detector match flags by name (aggregate counts only; matched content is never stored), severity, enforce-coverage posture, unapproved tools.
Live activity trail: streaming per-event view with a 1-10 security score, pseudonymous host and user refs, model, tokens, cost, and match flags.
Fleet: enrolled devices, collector coverage %, healthy / silent / dead / never-seen, attribution health, and coverage SLO.
Same Home chrome in light theme (dark default above).
Three ways in, from fastest local eval to a private-network pilot. Personal mode is the default story; Docker is the full-stack demo.
No company, no SSO, no Docker, no database. Install the single aim CLI and
watch your own Claude Code, Cursor, Kilo Code, Kimi Code, Grok Build,
and GitHub Copilot usage on a local dashboard. Everything stays on your machine.
personal mode makes zero outbound network calls (verify by running it
with networking off). Requires only Python 3.11+ (standard library only).
On a single-user machine user_ref falls back to host_ref. There is
no separate person mapping until you run the fleet identity-sync path.
pipx install aimonitoring-security
aim personal
# → open http://127.0.0.1:8787
aim --version # e.g. "aim 0.1.4"Do not run
pipx install aim. That PyPI name is AimStack's unrelated ML experiment tracker. Our distribution name isaimonitoring-security; the console script it installs is stillaim.
aim personal --help # flags + the zero-outbound claim
aim personal # scan once + serve the dashboard
aim personal --watch # also re-scan every 30s while open
aim personal --port 9000
aim personal --scan-only # just refresh the local store, no server
aim status # local, network-free: what's installed + queued
aim --versionBuild the same wheel from this repo (offline / no PyPI):
python3 scripts/build_aim_cli.py
pipx install packaging/aim-cli/dist/aimonitoring_security-*-py3-none-any.whl
aim personalSee packaging/aim-cli/README.md.
It reads your real local AI tool data: Claude Code transcripts
(~/.claude/projects/**/*.jsonl), Cursor's local state (state.vscdb),
Kilo Code task logs (ui_messages.json), and Kimi Code wire logs
(wire.jsonl), and extracts metadata only (model, token counts,
session/repo pseudonyms: never prompt text or code). Secret/PII patterns are
matched in memory at scan time and discarded immediately; only detector
names (e.g. secret:aws-access-key) are stored as match flags. Everything
lands in a local SQLite file at ~/.aim-collector/personal.db. The dashboard
binds 127.0.0.1 only. Single implicit local user; no auth.
Works on Windows / WSL / Linux / macOS.
For an engineer or small team evaluating the full stack on a laptop (loopback + demo seed; not only personal mode):
git clone https://github.com/hawikk/aim.git
cd aim
./scripts/demo-stack-up.sh
# → http://127.0.0.1:8081make demo-stack # same script
make demo-stack-preflight # Docker / ports / env onlyHappy path, failure modes (missing Docker, port conflicts, secret placeholders), health checks, optional Gatehouse pointers, and explicit non-goals:
→ docs/deployment/self-host-quickstart.md
This path is self-hosted Community (free, uncapped), not multi-tenant SaaS and not a CI product. For a real private-network company pilot use Enterprise / private-network pilot below. Hosted Cloud: request access.
Company control plane on an EC2 / VM / NetBird overlay. No manual env
exports. Datastores stay loopback; app surfaces publish for collectors; demo
seed stays off. On success: health green + enroll.sh device one-liner.
Cloud list price and Enterprise: pricing. The installer itself is in this repo:
git clone https://github.com/hawikk/aim.git
cd aim
./scripts/install-pilot.sh
# → ingest :8080 /healthz + dashboard :8081 /api/health = 200
# → prints: curl -fsSL http://…:8081/enroll.sh | bash -s -- --url … --token …
# Prefer prebuilt images when available:
# AIM_IMAGE_TAG=main-<shortsha> ./scripts/install-pilot.sh --pullmake install-pilot # prefer-pull; falls back to source build
make install-pilot-pull # require prebuilt images
make install-pilot-build # force source build
make install-pilot-preflight # Docker / ports / disk hints only| Default | Pilot value |
|---|---|
AIM_BIND_ADDR |
0.0.0.0 |
AIM_DATASTORE_BIND_ADDR |
127.0.0.1 |
| Demo seed | off |
| Corrupt Docker data-root | ./scripts/demo-stack-recover.sh then re-run |
Air-gapped and backup/restore procedures:
→ docs/deployment/air-gapped-install.md · docs/deployment/backup-restore.md
On a managed machine, one command detects every installed AI tool, hooks the hook-capable ones, enrolls the device once, verifies connectivity, and registers a per-user background watcher so collection survives reboots:
aim join https://ingest.corp.example --token <scoped-enroll-token>
aim status # read-only: per-tool hook / enroll / heartbeat / spool
aim doctor # verify install health
aim doctor --fix # repair: re-add clobbered hooks, restart service, drain spool
aim uninstall # remove hooks, state, and the auto-start service (idempotent)Auto-start is per-user, never root. join registers the watcher as a
systemd user unit (Linux), a launchd LaunchAgent (macOS), or a per-user
Scheduled Task (Windows): nothing is written to a system/root scope, and
aim refuses to install a service as root.
Survives the two silent-failure modes. Watchers no longer die with a
terminal (the service restarts them across reboots), and when an AI tool's
update rewrites its own settings file and drops our hook, aim doctor detects
that clobber and --fix re-installs the hook without losing any spooled
events.
Auditor one-pager: docs/privacy/auditor-privacy-overview.md:
two pages on the metadata-only posture plus offline verifier commands
(scripts/no_content_egress.py, compliance offline pack +
scripts/verify-compliance-bundle.mjs).
These are non-negotiable design constraints, enforced in code and tests:
- Metadata-only telemetry. The platform never stores prompt text, response
text, or code content. The canonical schema uses
additionalProperties: false, so any property named likeprompt,content,text, orcodeis rejected. Detection results are boolean flags plus a detector name: never matched content. You can verify this yourself in this repo, without a running stack:python scripts/no_content_egress.pywalks the whole invariant end to end (schema rejection, adapter strip-before-emit, and the ingest archive layer), and--self-testmutates each control to prove the checks still fail when they should.pnpm --filter @aimon/ingest testruns the schema and archive suites, including canaries for content smuggled through property names andschema_version. Both run in CI. - Pseudonymization at the edge.
user_ref,host_ref, andrepo_refare salted HMACs computed on the endpoint, before the event leaves the machine, so no raw identity is ever on the wire or in the telemetry store. The salt is held by the platform (secrets manager / KMS, security-role IAM only), so re-identification is possible, deliberately, because pure anonymity would make incident response impossible. It is also deliberately narrow: the identity-mapping service exposes a security-role-only, fully auditeduser_ref -> userlookup and nothing else resolves the mapping. Dashboards and general querying show pseudonyms or team-level aggregates only. Audited, role-restricted re-identification is the standard DPIA-friendly middle ground (EU / works-council posture); the reasoning is indocs/privacy/data-minimization-and-pseudonymization.md. - Data minimization. Only the fields in the canonical schema exist, each
with a documented privacy justification (
packages/schema/README.md). Rejected payloads are audited by SHA-256 hash and key names only: never stored raw. - Split enforcement posture. The platform guardrail engine is
detect-and-alert only: every finding carries
decision: "observe". That is intentional and must not be read as "we do not enforce." Claude Code, Cursor, VS Code Copilot, and Kimi Code can block on the endpoint when a managedenforcement.jsonis loaded (mode: enforce). Copilot CLI and Grok Build can deny tool calls only. Kilo Code has no third-party hook API and stays observe-only; seedocs/security/enforcement-capability-matrix.mdfor the per-collector table, backed structurally bycollectors/parity-matrix.json. Real blocks and shadow decisions are audited on usage events asenforcement: {action, rule_id, policy_hash}(blocked|would_block|confirmed). A missing bundle fail-opens to observe. Fleet enforce coverage:#/fleetandGET /api/enforcement/coverageshow install-path coverage, honor rate, and fail-open inventory so SOC can answer "who can enforce today?" without SQL. - Retention is enforced, not aspirational. Every store ages itself out by
data class: events 90d, findings 365d, audit trail 730d by default, with
audit ≥ findings ≥ eventsenforced. The ingest service purges Postgres in bounded batches on a schedule; personal mode prunes its local SQLite store. A bad config fails closed (skips + logs, never guesses). Knobs:RETENTION_EVENTS_DAYS/RETENTION_FINDINGS_DAYS/RETENTION_AUDIT_DAYS/RETENTION_DRY_RUN/RETENTION_INTERVAL_HOURS/RETENTION_BATCH_SIZE(see.env.exampleanddocs/privacy/data-minimization-and-pseudonymization.md).
Path of record, component table, and data-flow detail: docs/architecture.md (kept current; this section is a sketch).
endpoints / CI network / IdP
┌────────────────────┐ ┌──────────────────┐
│ aim join + hooks │ │ proxy / os-egress│
│ claude/cursor/… │── events ────▶│ shadow-ai IdP │── events ──┐
│ aim watch (user) │ metadata └──────────────────┘ metadata │
└────────────────────┘ only only │
│ ▼
└──────────────▶ services/ingest (auth + schema + Postgres + archive)
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
apps/api guardrail gatehouse / hygiene
apps/web findings PR + full-history scans
│ │
└──────── alert bus ┴──▶ sentinel / SIEM
- Collectors and proxy pipelines emit metadata-only events (or match flags only for secrets/PII).
- Ingest authenticates, validates against the schema
(
additionalProperties: false), writes Postgres + optional archive. - Guardrail evaluates events against
policies/guardrail/v1/core.yaml. - Findings surface in the dashboard; high-value signals can page via the alert bus when destinations are configured.
- Gatehouse publishes onto the same
security.alertbus shape where wired.
Personal mode (aim personal) Demo stack (demo-stack-up.sh) Company pilot
local SQLite, :8787 loopback compose, :8081 install-pilot.sh
zero outbound seeded eval private network
apps/api Dashboard API (SSO/RBAC, findings, fleet, coverage, …)
apps/web Analyst console (static ES modules + views)
apps/landing Public product page
services/ingest Enroll / heartbeat / events (Postgres + object archive)
services/guardrail Post-ingest policy engine → findings
services/gatehouse Free PR-security pillar (Semgrep/Gitleaks/Checkov/Trivy)
services/sentinel Alert bus consumer / triage / optional draft PRs
services/hygiene Full-history secret hygiene
services/identity-sync Directory join + pseudonym reveal
services/shadow-ai IdP OAuth / SaaS grant inventory
collectors/* Endpoint + proxy collectors (claude, cursor, kilo, kimi, grok, …)
packaging/aim-cli `aim` CLI (join / watch / doctor / personal)
packages/schema Canonical event / alert / finding contracts
policies/ Guardrail + MCP + compliance policy-as-code
deploy/ Helm, Linux/Windows/macOS, Intune, enforcement bundles
docker-compose.yml Full local stack (dashboard typically :8081)
docs/architecture.md Component map + path of record
Prereqs: Node 22+ (nvm use), pnpm 9 (corepack enable), Docker.
pnpm install
pnpm build
npm ci --prefix apps/api # apps/api is npm-managed (own lockfile)
pnpm typecheck
pnpm lintpnpm test is not usable in this snapshot: services/ingest runs
vitest run, and the workspace test files are not part of the public export,
so vitest exits non-zero on "no test files found". Run the suites listed under
Tests in this snapshot instead.
Heads up: this public repo is a snapshot of a larger working repo and does not carry most of the per-collector and per-service
tests/directories, sopytest collectors/...will collect nothing here. The one deliberate exception isservices/ingest/test/, which carries the schema and archive suites because they are the executable proof of the metadata-only claim above. What is present and running in CI is listed below.
The per-tool endpoint collectors are pure-stdlib Python, and so is the
packaged aim CLI: that is why the shipped wheel has no runtime
dependencies. Two support packages under collectors/ are exempt and do have
dependencies: collectors/integrity needs cryptography to verify signed
config bundles, and collectors/adapter needs PyYAML (plus jsonschema,
optionally) to load and validate manifests. The services declare their deps in
their own pyproject.toml.
pip install -r requirements-dev.txt # pytest + jsonschema + editable services
python -m pytest scripts -q # enrol script + DPIA exporter
python packages/schema/validate.py # canonical schema + conformance corpus
python scripts/build_aim_cli.py # `aim` wheel + sdist (stdlib only)
node --test scripts/test_compose_bind.mjs # compose never binds 0.0.0.0
node --test scripts/test_compose_pull.mjs # pilot pull-override contract
pnpm lint && pnpm typecheck # after `pnpm install`The full set of checks that gate main is in
.github/workflows/ci.yml; see
CONTRIBUTING.md for the rest of the contributor workflow.
Prefer the one-command path (preflight + mint env + up + health + seed):
./scripts/demo-stack-up.sh # or: make demo-stack
# → http://127.0.0.1:8081Manual equivalent:
python3 scripts/ensure_dev_env.py
docker compose up -d --build
SEED_BASE_URL=http://localhost:8080 ./scripts/seed-pilot-cohort.sh
# open http://localhost:8081The stack opens on http://localhost:8081 with a local admin identity
(personal/standalone mode: no AIM_OIDC_* set). SSO is only wired for a
company deploy. identity-sync loads the fixture directory at startup so team
attribution works out of the box. guardrail polls evaluate-db on an
interval, so the Security view populates as events flow.
Notes:
- Every published port binds
127.0.0.1by default. Nothing is reachable from your network until you say so. To let collectors on other machines reach ingest, setAIM_BIND_ADDR=0.0.0.0in.env. That knob is separate fromAIM_DATASTORE_BIND_ADDR, so widening the app surfaces never widens the telemetry database. See.env.example. - If you run Docker in WSL2 and browse from Windows,
localhost:8081forwards to the distro's loopback automatically. The WSL IP does not. That address is no longer bound, which is the point. - A blank page at
localhost:8081almost always means the stack isn't running on that machine:docker compose psshould showapiasUp.
- No secrets in the repo: tokens/passwords come from env vars;
.envfiles are gitignored (only.env.exampleis committed). - Bearer tokens for ingest come from
INGEST_TOKENS(comma-separated) and are compared in constant time;Authorizationheaders are redacted from logs. - Containers run as a non-root user (
node). - The ingest service logs ids and counts only, never event payloads, and validation errors never echo payload values back to callers.
- Database writes are parameterized; ingest is idempotent on
event_id(ON CONFLICT DO NOTHING).
The dashboard API (apps/api) terminates SSO itself: with AIM_OIDC_* set
it runs an in-app OIDC authorization-code flow (PKCE, HMAC-signed HttpOnly
session cookie) and maps the ID token's groups claim to roles via
AIM_ROLE_GROUPS_*. No authorization decision ever derives from a
client-supplied header: X-Forwarded-* / X-AIM-* identity headers are
never read; there is no proxy-auth mode. With no AIM_OIDC_* set the API
runs in personal/standalone mode (a single local admin identity), which must
never be exposed beyond localhost.
Four roles, enforced in apps/api/src/auth.js:
- viewer: org/team aggregates and dashboards only: no per-engineer rows, no findings, no audit trail.
- auditor: read-only: dashboards, compliance views, and the access audit trail.
- analyst: dashboards, findings console, user-level rows, fleet, coverage: no guardrail config, no audit trail.
- admin: everything, including repo de-pseudonymization labels and guardrail configuration.
Groups come from AIM_ROLE_GROUPS_ADMIN (default ai-monitoring-security),
AIM_ROLE_GROUPS_ANALYST, AIM_ROLE_GROUPS_AUDITOR, and
AIM_ROLE_GROUPS_VIEWER (default ai-monitoring-viewers). An authenticated
user whose groups map to no role gets zero access: fail-closed.
Viewer is an explicit group grant, not an implicit default. Identity
reveal is not a role and is not bundled into admin: it is a separate
capability from AIM_REVEAL_GROUPS (default ai-monitoring-revealers).
See docs/identity-mapping-design.md.
Browser identity comes from the aim_session cookie. Daemons use a
non-interactive credential:
node scripts/mint-service-token.mjs sentinel --role analystThat prints the secret once and the entry to add to the file named by
AIM_SERVICE_TOKENS_FILE. The file stores sha256 digests, not secrets.
Consumers authenticate with Authorization: Bearer <secret>.
- A service token may not hold
admin: onlyviewer,analyst, orauditor. A disallowed role is rejected at load, never quietly downgraded. - A
Bearerheader is authoritative: it resolves to a service identity or 401s. It does not fall through to the cookie/personal paths. - A configured-but-unparseable token file is a fatal boot error.
Apache License 2.0. See LICENSE.
“AIM” and “AI Monitoring” are trademarks of AI Monitoring.
Site: getaimonitoring.com · Security: security@getaimonitoring.com




