Skip to main content

Afterlife

CI Python License: MIT

Credentials that outlive their owners.

Afterlife demo: 20 findings, 16 persons across 8 sources, cross-source identity graph

Afterlife is a ghost-access auditor. It pulls identities and credentials from your cloud, code, IdP, and SaaS systems into one identity graph, then runs detection rules over that graph and ranks findings by blast radius.

The class of credentials it surfaces drives a disproportionate share of modern breaches: every API key generated by a contractor who left two years ago, every OAuth grant tied to a deprovisioned employee, every long-lived AWS access key on an account marked "suspended" in the IdP. Uber 2022, Okta 2023, MOVEit, Snowflake 2024 all involve some version of this.

Most tools treat each system in isolation. Afterlife's value is the cross-source identity graph: an AWS access key's "owner" might be active in AWS but suspended in Google Workspace; without joining the two views, you miss it.

Status

v0.3. 9 source systems collected, 16 detection rules, 380+ tests.

Source systems

Cloud AWS IAM, GCP IAM
Code hosting GitHub, GitLab
Identity providers Google Workspace, Microsoft Entra ID, Okta
Operational Slack, HashiCorp Vault

Plus a CloudTrail enrichment collector (afterlife scan cloudtrail): it adds no identities, but reads recent CloudTrail events to attach observed last-use and used-services to the AWS credentials, giving the usage-based rules ground-truth data. Run it after scan aws.

Each collector lives in src/afterlife/collectors/. They are intentionally dumb (no analysis), idempotent (re-run safe), and tested against mocked APIs (no live calls in CI). Vault's aliases field is used to add cross-system graph edges directly, even without shared email.

What it detects

Rule Severity What it catches
OFFBOARDED-OWNER Critical Active credential whose owner (or any cross-source linked identity) is suspended/archived/deleted in an IdP. The Uber-2022 pattern.
CROSS-ACCOUNT-TRUST Critical IAM role trusts an external AWS account. The Capital-One-2019 precondition.
PUBLIC-ROLE-TRUST Critical IAM role assumable by any AWS principal (wildcard Principal) with no restricting condition.
ADMIN-CONCENTRATION Critical Same person holds admin-tier access in 2+ systems (IdP admin flag + AWS AdministratorAccess + ...).
ADMIN-WITHOUT-MFA Critical IdP admin (Google Workspace today) without 2-step verification enforced.
UNUSED-CREDENTIAL High Active credential not used in N days (default 90).
STALE-DEPLOY-KEY-WRITE High Write-capable deploy key not used in N days.
STALE-OAUTH High Write-scoped third-party OAuth grant not used in N days (default 90).
OUTSIDE-COLLAB-WITH-AWS High GitHub outside collaborator linked to active AWS credentials.
ORPHANED-GITHUB High Active GitHub PAT whose owner is no longer in the org (Enterprise SAML).
INACTIVE-ADMIN High Admin who hasn't logged in within the inactivity window.
UNROTATED-KEY Medium Long-lived static cloud key (AWS / GCP) past the rotation threshold.
PRIVILEGE-DRIFT Medium IAM role granted far more AWS services than it uses (Access Advisor, refined by CloudTrail).
USER-WITHOUT-MFA Medium Active non-admin user (Google Workspace today) with no 2-step verification. The Snowflake-2024 pattern.
NEVER-USED Medium Active credential past the grace period with no usage record.
ORPHANED-IDENTITY Low IdP identity with no downstream system presence (hygiene signal).

Each rule's logic, false-positive notes, and remediation are in docs/DETECTIONS.md.

Quickstart

Zero-config demo against in-memory mocks for eight of the nine source systems (all but Okta):

make install
make demo

The demo plants synthetic users, credentials, and IdP records across every collector, runs them, and produces 20 deterministic findings with one OFFBOARDED-OWNER (bob, broad blast) and one ADMIN-CONCENTRATION (dave is admin in 3 systems). Identity graph: 16 persons across 8 sources, 6 cross-source. Demo also writes .afterlife-demo-report.html you can open in a browser.

Against real systems:

make install
.venv/bin/afterlife init
.venv/bin/afterlife scan aws --profile my-profile
.venv/bin/afterlife scan cloudtrail --profile my-profile   # enrich AWS usage
.venv/bin/afterlife scan gcp --project my-project
.venv/bin/afterlife scan github --org my-org --token $GITHUB_TOKEN
.venv/bin/afterlife scan gitlab --group my-group --token $GITLAB_TOKEN
.venv/bin/afterlife scan idp --provider google     # or okta / azure
.venv/bin/afterlife scan slack --token $SLACK_TOKEN
.venv/bin/afterlife scan vault --api-url https://vault.example.com:8200
.venv/bin/afterlife analyze --allowlist allowlist.yaml
.venv/bin/afterlife identities
.venv/bin/afterlife report --format html -o report.html
.venv/bin/afterlife serve                          # localhost dashboard

See .env.example for required environment variables.

Reports

afterlife report emits four formats. Each carries the same finding set; choose based on consumer.

Format Use it for
json Programmatic consumption, scripting, pipelines
html Self-contained audit handout, attach to a PR or email
pdf Publication-ready handout for stakeholders (requires [pdf] extra and Pango)
sarif GitHub Code Scanning, Azure DevOps, GitLab security feeds
.venv/bin/afterlife report --format pdf -o audit.pdf

Web dashboard

afterlife serve launches a local FastAPI dashboard with nine pages.

Overview: severity tiles, blast tiers, identity-graph stats Findings list with filter, sort, suppression toggle Identity graph: alice 7-way cross-source, suspended/archived statuses surfaced in red

More screenshots
Credentials across all sources Credentials, continued
Credentials Credentials, continued
Findings (medium / low tiers, orphaned identities) Overview, bottom (top findings by blast radius)
Findings, more Overview, bottom
Identities, mixed-source middle Identities, single-source bottom (bots, no-email cases)
Identities, middle Identities, bottom

Every scan run captured by the operational scan-history page:

Scan history: 8 collectors run with status, records, duration

The pages:

  • Overview: severity tiles, blast-tier chart, last-scan-per-source.
  • Findings: filterable, searchable, sortable, expandable evidence + remediation, one-click ack per finding (state in localStorage), HTMX-powered live filtering.
  • Trends: finding history over time from the lifecycle timestamps: open findings by severity, new-vs-resolved flow, and headline stats (open now, seen ever, resolved, median days to resolve).
  • Credentials: sortable table with source / type / active filters, click into per-credential detail.
  • Identities: person-grouped, filterable to cross-source only, click into per-person detail showing all linked identities + owned credentials + active findings.
  • Finding / Credential / Person detail pages: deep-linked, fully cross-referenced.
  • Scan history: every afterlife scan ... run with start/end/duration/status.

The dashboard is read-only: no DB writes, no auth, hardened with a strict CSP, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Cross-Origin-Opener-Policy: same-origin, disabled OpenAPI/docs endpoints, and self-hosted HTMX (no CDN). Dark mode follows prefers-color-scheme. Keyboard shortcuts (/ to search, g h/f/c/i to navigate, ? for help). Includes a print stylesheet for PDF-via-browser.

Alerting

Afterlife tracks findings across runs (each analyze reports what is new, reopened, or resolved since the last run), so it can alert you the moment new ghost access appears rather than only when you go looking.

export AFTERLIFE_SLACK_WEBHOOK=https://hooks.slack.com/services/...
.venv/bin/afterlife analyze --notify        # alert on new/reopened findings

Channels (any combination, configured via environment variables, never persisted):

Channel Configure with
Slack AFTERLIFE_SLACK_WEBHOOK (Incoming Webhook URL), or --slack-webhook
Webhook AFTERLIFE_WEBHOOK_URL (alerts POSTed as JSON), or --webhook
Email AFTERLIFE_SMTP_HOST + AFTERLIFE_EMAIL_TO (plus optional SMTP auth)
Jira (Pro) AFTERLIFE_JIRA_URL + _EMAIL + _TOKEN + _PROJECT (files a remediation ticket per new finding)

Only new and reopened findings at or above a severity threshold are sent (AFTERLIFE_NOTIFY_MIN_SEVERITY, default high; or --notify-min-severity), and suppressed findings are never alerted. Run it on a schedule (cron, the CI workflow below) to turn Afterlife into a continuous monitor. See .env.example for every variable.

Continuous monitoring

afterlife run executes the whole pipeline in one pass (scan the configured sources, analyze, and with --notify alert), and afterlife watch repeats it on an interval. Both self-initialize the database, read credentials from the environment, and skip any source that is not configured, so one broken credential never stops the monitor.

# one pass over whatever the environment has credentials for
.venv/bin/afterlife run --notify

# or run continuously, hourly, over an explicit source list
.venv/bin/afterlife watch --interval 3600 --notify -s aws -s github -s idp

Keep the source list and cadence declarative with a config file (see afterlife.example.yml):

.venv/bin/afterlife watch --config afterlife.yml

Docker

docker build -t afterlife .
docker run --rm -v afterlife-data:/data --env-file .env \
    afterlife watch --notify -s aws -s github

The image runs as a non-root user and keeps its database on the /data volume; see the Dockerfile.

CI integration

# .github/workflows/afterlife.yml (excerpt)
- run: afterlife report --format sarif -o afterlife.sarif
- uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: afterlife.sarif

Full workflow at .github/workflows/afterlife.yml. It assumes an AWS role via OIDC, scans every source the team uses, uploads SARIF to Code Scanning, and saves an HTML report as a 30-day artifact.

Allowlist / suppression

afterlife analyze --allowlist allowlist.yaml reads a YAML file naming findings to suppress. Suppressed findings are persisted (still auditable) but hidden from the default dashboard view. Example:

- rule_id: NEVER-USED
  credential_id: arn:aws:iam::123:role/SeasonalReportingRole
  reason: Yearly audit role, intentionally dormant
  until: 2027-01-01

Matchers: rule_id, credential_id, identity_source, identity_id. All named fields must match. Catch-all entries (no matchers) are refused at load time.

Editions

Afterlife is open core. Everything above is free and always will be: detection, the identity graph, blast scoring, monitoring with history, alerting, run / watch, reports, and the local dashboard.

Pro adds team/enterprise features, unlocked by an offline license key (a signed token verified locally against an embedded public key -- no license server, nothing phones home):

Pro feature
Dashboard authentication afterlife serve --require-auth password-protects the dashboard so you can safely expose it to a team
Single sign-on (OIDC) afterlife serve --sso puts the dashboard behind your identity provider (Google, Okta, Entra, Auth0, Keycloak) with an optional email/domain allow-list
Ticketing integrations Files a Jira issue for new and reopened findings on afterlife analyze --notify, turning ghost access into tracked work

Get Pro (from $990/year, founding rate, first 50 organizations, price locked for life): email didesle7@gmail.com with your organization name and you'll get a signed license key to activate:

afterlife license                       # show your edition
export AFTERLIFE_LICENSE=<token>        # activate (or AFTERLIFE_LICENSE_FILE=<path>)

Maintainers mint licenses with scripts/issue_license.py using the vendor private key, which is never committed.

Architecture

  collectors/  ─►  SQLite  ─►  identity graph  ─►  rules engine  ─►  blast scoring  ─►  reports
   AWS / GCP                   (NetworkX,           (pluggable)        (per-finding)     (json/html
   GitHub / GitLab              email + Vault                                             /sarif/pdf)
   Google / Okta / Azure        aliases)
   Slack / Vault

Five layers, each with a narrow boundary. Collectors write to SQLite only. Rules read from SQLite + identity graph only. Scoring is pure (input: credential, output: blast radius). Reports are pure (input: DB, output: text). The dashboard wraps the same readers behind FastAPI. Details in docs/ARCHITECTURE.md.

Project layout

src/afterlife/
├── collectors/    9 source collectors + a CloudTrail usage-enrichment pass
├── rules/         16 detection rules, one file each, decorator-registered
├── graph/         Identity graph (NetworkX), email + Vault-alias linking
├── scoring/       Blast-radius scoring with explainable factors
├── reporting/     JSON, HTML, SARIF, PDF
├── notify/        Alerting: Slack, webhook, email (SMTP)
├── web/           FastAPI dashboard + templates + static assets
├── runner.py      Pipeline: scan -> analyze -> notify (run / watch)
├── allowlist.py   YAML suppression loader + matcher
├── licensing.py   Offline Pro-license verification (open core)
├── scan_runs.py   Run-tracking context manager
├── db.py          SQLite schema + helpers
├── models.py      Identity, Credential, Finding, BlastRadius
└── cli.py         Typer CLI

tests/             380+ tests using moto, respx, freezegun, fastapi.testclient
demo/              Self-contained `make demo` (mocks for every collector)
docs/              ARCHITECTURE.md, DETECTIONS.md, INTERVIEW_TALK_TRACK.md
.github/workflows  Production-ready GitHub Action

Further reading

Why "Afterlife"

The credentials this tool finds shouldn't still be alive.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

afterlife_audit-0.3.1.tar.gz (2.0 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

afterlife_audit-0.3.1-py3-none-any.whl (151.4 kB view details)

Uploaded Python 3

File details

Details for the file afterlife_audit-0.3.1.tar.gz.

File metadata

  • Download URL: afterlife_audit-0.3.1.tar.gz
  • Upload date:
  • Size: 2.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for afterlife_audit-0.3.1.tar.gz
Algorithm Hash digest
SHA256 f04dcda49d190a4e86f6e5cedd2a72d259c77664922fffafee4ef2e81295e12c
MD5 92c8456a1549c38ef53ea937e14bf4f9
BLAKE2b-256 c0f7e2c0a9d4cd29adc3fa46f6b403008df69391417d75081214fbfc4c9e5fa6

See more details on using hashes here.

Provenance

The following attestation bundles were made for afterlife_audit-0.3.1.tar.gz:

Publisher: release.yml on desledishant10/afterlife

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file afterlife_audit-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: afterlife_audit-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 151.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for afterlife_audit-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d7908f70e0cd2c3cba90daf7d34538c4450e3c96ed7bbc0b837d07d04d319f9a
MD5 2bb12e23ca3782e766c6baa167dcaec8
BLAKE2b-256 2274a898c20d4539efd5036855922a981477ddba935d861b90543f6e2f0b7727

See more details on using hashes here.

Provenance

The following attestation bundles were made for afterlife_audit-0.3.1-py3-none-any.whl:

Publisher: release.yml on desledishant10/afterlife

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.3.2

2 files

This release

0.3.1 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page