A read-only TypeScript collector that captures AWS, GCP, and Kubernetes configuration evidence for the FedRAMP 20x Key Security Indicators. It accounts for the full 223-requirement FedRAMP 20x set (63 KSIs + 160 FRRs): 44 KSIs run live cloud collectors across IAM, CNA, MLA, CMT, SVC, RPL, PIY, SCR, INR, AFR, and CSX; the remaining governance/process requirements emit signed process-artifact evidence (attestation register) or are tracked as awareness-only. Pick an impact tier (Low / Moderate / High) and benchmark the result against NIST SP 800-53 — see the root README and RUNBOOK for the full feature set.
The sections below cover collector setup and the read-only model. For impact levels, the
--frameworkNIST benchmark, output artifacts, and integrations, see the root README.
The collector must never mutate cloud state. This is enforced five ways:
- Every SDK call across
providers/aws/*.ts,providers/gcp/*.ts, andproviders/k8s/*.tsis a read verb:Get*/List*/Describe*/Search*/Export*/Recommend*. NoPut/Create/Update/Delete/Modify/Attach/Detach/Set*calls anywhere. - AWS runtime guardrail. Every AWS SDK client is wrapped by
core/readonly-guardrail.ts, which proxies.send()and inspects the Command class name. Non-read verbs throwReadOnlyViolationErrorbefore the call leaves the process. - GCP runtime guardrail. Every GCP client returned by
core/auth/gcp.ts::googleClient()andguardGcp()is wrapped by a recursive Proxy incore/readonly-guardrail-gcp.tsthat classifies every method call by verb prefix and throwsReadOnlyGcpViolationErroron anything matchingcreate*/update*/delete*/set*/patch*/insert*. Symmetric with the AWS guardrail; can be temporarily disabled for debugging viaCLOUD_EVIDENCE_DISABLE_GCP_GUARDRAIL=1(DO NOT use in production). - Required cloud IAM roles are read-only managed policies only. See "Required cloud permissions" below.
- No write path to the tracker DB that bypasses bearer-token-authenticated
/api/collector-runs. Collector cannot directly mutate the tracker.
If you ever add a new collector, both guardrails will block any inadvertent
mutating call at runtime. Lint your changes by running npm run typecheck
and a --dry-run smoke test.
- Node 22+ (developed against Node 24). The collector also runs on Bun 1.3+ (recommended for production) and Deno 2.8+ — see the RUNBOOK "Runtime" section.
- AWS credentials: the runner has an active session via
aws sso loginorAWS_PROFILEset to a profile with read-only access (see roles below). - GCP credentials:
gcloud auth application-default loginalready run, so Application Default Credentials are present.
cd "/Users/kenith.philip/FedRAMP 20x/cloud-evidence"
npm installEdit config.yaml:
aws:
enabled: true
regions: [us-east-1] # primary region; expand later
prod_tag: { key: env, values: [prod, production] }
gcp:
enabled: true
organization_id: null # set if you want org-wide queries
projects: [your-prod-project-id]
prod_label: { key: env, values: [prod, production] }thresholds.yaml controls per-severity finding rollup behavior. See the
file for documentation; tune the defaults block and add per-KSI
overrides as needed.
Plan only (no SDK calls):
npx tsx core/orchestrator.ts --dry-runReal collection, all supported KSIs across both providers (default tier Moderate):
npx tsx core/orchestrator.ts
# or pick a tier + NIST benchmark framing:
npx tsx core/orchestrator.ts --impact-level high --framework rev5Select a subset of KSIs:
npx tsx core/orchestrator.ts --ksis KSI-IAM-MFA,KSI-IAM-AAMOne provider only:
npx tsx core/orchestrator.ts --providers awsOutput goes to ./out/ (gitignored). One JSON file per KSI plus a top-level
pva-run-summary.json.
The simplest setup is the AWS-managed ReadOnlyAccess policy. It
covers every API the collector calls. If you prefer least-privilege, the
following AWS-managed policies (all read-only) cover Phase 1:
IAMReadOnlyAccessAWSSSOReadOnly(IAM Identity Center)AWSOrganizationsReadOnlyAccessAWSSecurityHubReadOnlyAccessIAMAccessAnalyzerReadOnlyAccessAmazonGuardDutyReadOnlyAccessAmazonEventBridgeReadOnlyAccessAWSLambda_ReadOnlyAccessAmazonSSMReadOnlyAccessAmazonCognitoReadOnlysts:GetCallerIdentity(in your inline trust)
The collector does not require any Put*/Create*/Update*/Delete*
permission, and the runtime guardrail blocks such calls even if a policy
were over-broad.
Grant your principal (the user behind ADC, or an audit SA) these roles on each in-scope project:
roles/iam.securityReviewer(IAM bindings + SA inventory)roles/recommender.viewer(Recommender findings)roles/orgpolicy.policyViewer(Org policy constraints)roles/accesscontextmanager.policyReader(CAA policies)roles/logging.privateLogViewer(audit logs / IAM Audit Configs read; needed for later phases)roles/eventarc.viewer(Eventarc triggers)
For Identity Platform tenant inspection and Workforce Identity Federation pool listing, add (at the appropriate scope):
roles/identitytoolkit.viewer- Org-level:
roles/iam.workloadIdentityPoolViewer(if pools are at org level)
Workspace / Cloud Identity admin APIs (e.g. 2SV enforcement settings) need admin scopes that ADC may not have. The GCP collectors degrade gracefully when those are unavailable — affected findings surface as warnings, not failures.
A run writes one KSI-*.json evidence envelope per requirement plus the
roll-ups and (signed) reports:
out/
pva-run-summary.json # top-level run summary (impact level + framework + benchmark headline)
family-rollup.json # per-control-family posture
control-benchmark.json # NIST 800-53 control benchmark for this run's framing/level
KSI-IAM-MFA.json # per-KSI evidence envelope (one per requirement) …
manifest.json # Ed25519-signed inventory of every output file
manifest.sig
run-ledger.jsonl # append-only audit trail of every action + timing
… # OSCAL / crosswalk / coverage / diff / report.html (opt-in)
See the root README for the full artifact table. Each per-KSI file follows the envelope shape documented in ksi-deep-analysis.md.
44 of the 63 KSIs run live cloud collectors (the rest are governance/process
requirements satisfied via the attestation register or tracked awareness-only).
Run npx tsx core/orchestrator.ts --dry-run to print the exact in-scope set for
your config and tier. Collectors live under providers/aws/*.ts and
providers/gcp/*.ts, grouped by domain (iam, network, logging, config, data,
secrets, backup, supplychain, inventory), plus providers/k8s/security.ts. The
authoritative requirement registry is docs/frmr-requirements.generated.json
(regenerated by scripts/extract-frmr-requirements.mjs).
cloud-evidence/
config.yaml # account/project scope thresholds.yaml # finding rollup config
core/
orchestrator.ts # CLI entry point
ksi-map.ts # master KSI -> collector map (44 cloud KSIs)
envelope.ts / findings.ts # evidence envelope + finding/rule/rollup helpers
schema.ts / sign.ts / timestamp.ts / oscal.ts # validation, signing, RFC 3161, OSCAL
control-benchmark.ts # NIST 800-53 control benchmark (20x + Rev5)
requirements-registry.ts / process-artifact-tracker.ts # level scoping + process evidence
run-ledger.ts / run-lock.ts / rate-control.ts # production hardening
readonly-guardrail.ts / readonly-guardrail-gcp.ts # runtime read-only enforcement
auth/ (aws.ts, gcp.ts, k8s.ts)
providers/
aws/*.ts gcp/*.ts k8s/security.ts # per-domain collectors
scripts/ # reproducible data extractors (FRMR, NIST r5, baselines)
docs/ # committed generated lookups + IAM-PERMISSIONS-CATALOG.md
tests/ # vitest suites (38 files, 396 tests)
out/ # generated evidence files (gitignored)
AWS auth failed: runaws sso login(if SSO) orexport AWS_PROFILE=<profile>. Verify withaws sts get-caller-identity.GCP auth failed: rungcloud auth application-default login. Verify withgcloud auth application-default print-access-token.ReadOnlyViolationError: a collector tried to dispatch a non-read-only Command. This is a defect in the collector — file an issue; do not work around the guardrail.- Many warnings about "not enabled or no permission": expected if a feature isn't in use (e.g. PAM, Identity Platform). Warnings are informational; check
pva-run-summary.jsonto see which KSIs actually failed vs. just warned. getIamPolicyreturns minimal bindings: passrequestedPolicyVersion: 3(already done in code) to receive conditional bindings.