Afterlife
Credentials that outlive their owners.
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.
More screenshots
| Credentials across all sources | Credentials, continued |
|---|---|
| Findings (medium / low tiers, orphaned identities) | Overview, bottom (top findings by blast radius) |
|---|---|
| Identities, mixed-source middle | Identities, single-source bottom (bots, no-email cases) |
|---|---|
Every scan run captured by the operational scan-history page:
The pages:
- Overview: severity tiles, blast-tier chart, last-scan-per-source.
- Findings: filterable, searchable, sortable, expandable evidence + remediation, one-click
ackper 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 |
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, and the line is simple: you pay for team, scale, and
operational surface, never for detection. Free forever: all 16 detection rules,
the identity graph, blast scoring, monitoring with history, alerting, run /
watch, reports, and the local dashboard including the trends view with MTTR and
burn-down.
Pro is not the finding, it is the team layer around it: shared, authenticated access, ticketing, and signed audit-evidence packs. It is 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 |
| Audit-evidence packs | afterlife evidence writes a signed, tamper-evident attestation of your access review (open + resolved findings, MTTR, scan history) that a SOC 2 / ISO auditor accepts as-is. afterlife verify-evidence checks it, and is free so anyone can verify |
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
- docs/PLAYBOOK.md: complete usage walkthrough (start here for hands-on)
- docs/ARCHITECTURE.md: layered design + why these boundaries
- docs/DETECTIONS.md: every rule, false-positive notes, remediation
- docs/blog/the-graph-layer.md: design essay on why a graph is the right shape for cross-source ghost-access detection
- docs/INTERVIEW_TALK_TRACK.md: prepared narratives for portfolio conversations
- docs/KEY-MANAGEMENT.md: backing up and rotating the license-signing key (maintainers)
- CHANGELOG.md: full release + milestone history
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file afterlife_audit-0.3.2.tar.gz.
File metadata
- Download URL: afterlife_audit-0.3.2.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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
69b024abba49ae3a6d53910e445c29937119d8ea891790f8ae9edb45c2517964
|
|
| MD5 |
b0aaf049bef277387b1ecc956a0f64ba
|
|
| BLAKE2b-256 |
1b4ff0c4749495b29bea0d9e01a3fb81f4babaa22dfa1c5ff7ba3de21acb1bfc
|
Provenance
The following attestation bundles were made for afterlife_audit-0.3.2.tar.gz:
Publisher:
release.yml on desledishant10/afterlife
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
afterlife_audit-0.3.2.tar.gz -
Subject digest:
69b024abba49ae3a6d53910e445c29937119d8ea891790f8ae9edb45c2517964 - Sigstore transparency entry: 2694218313
- Sigstore integration time:
-
Permalink:
desledishant10/afterlife@020461ef0921637b30d09097f23745a7f880d830 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/desledishant10
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@020461ef0921637b30d09097f23745a7f880d830 -
Trigger Event:
push
-
Statement type:
File details
Details for the file afterlife_audit-0.3.2-py3-none-any.whl.
File metadata
- Download URL: afterlife_audit-0.3.2-py3-none-any.whl
- Upload date:
- Size: 159.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a4f1958f8f050a851cb78a43730c49523c4fa40e68e54a5ded4cf44959ade800
|
|
| MD5 |
acd20cbb29581d7c19c6f7e6d5c77744
|
|
| BLAKE2b-256 |
3548cc4d9887224fe1ed90a0967bab0e1e56b6b429d27abb67a0465f80619718
|
Provenance
The following attestation bundles were made for afterlife_audit-0.3.2-py3-none-any.whl:
Publisher:
release.yml on desledishant10/afterlife
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
afterlife_audit-0.3.2-py3-none-any.whl -
Subject digest:
a4f1958f8f050a851cb78a43730c49523c4fa40e68e54a5ded4cf44959ade800 - Sigstore transparency entry: 2694218379
- Sigstore integration time:
-
Permalink:
desledishant10/afterlife@020461ef0921637b30d09097f23745a7f880d830 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/desledishant10
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@020461ef0921637b30d09097f23745a7f880d830 -
Trigger Event:
push
-
Statement type: