Repository navigation
v0.5.0-beta
Pre-releaseAdded
-
CrowdStrike Falcon fetcher set — seven fetchers and a fourteenth
category, contributed by @StewartClaw:
managed host inventory, Spotlight vulnerabilities, detections, prevention
policies, Zero Trust Assessment, FileVantage file integrity, and host firewall
policies and rules. All seven share one OAuth2 client
(_shared/falcon_client.py)
that handles token refresh, cursor and offset pagination, and the
Retry-Aftercontract on 429s. Commercial and GovCloud both supported —
cloud_regionselects a commercial region orus-gov-1/us-gov-2, and the
client follows the region-discovery redirect Falcon's auth endpoint issues
when the host is wrong.Built without a Falcon tenant, so correctness is pinned to CrowdStrike's
published API models rather than to hand-written fixtures:
tools/crowdstrike_schema.pyderives the request/response shapes,
tools/crowdstrike_schema_check.pyfails
when a fetcher reads a field the published model does not define,
tools/crowdstrike_usage_check.pyfails when a declared API scope isn't
exercised (and the reverse), andexamples/crowdstrike_mock_run.yamlruns the
whole set against a schema-derived local double — no tenant, no credentials, no
network egress. That covers the documented contract, not a live tenant's
responses; prevention policies and Zero Trust Assessment are the two worth
re-checking against a real tenant, as no public response samples exist for
either. -
docs/ksi_mapping.md— the full fetcher ↔ KSI
mapping, readable in both directions, with FedRAMP's verbatim statements and
NIST control crosswalk, and the open gaps called out as fetcher backlog.
Generated bytools/gen_ksi_mapping.py; the generator is idempotent, so CI can
fail on a diff to keep it honest. -
Issue-report fetchers (
kind: issue_report). A second collection kind for
the file a scanner already produces — a Nessus export, a Wiz findings CSV —
rather than a JSON payload asserting a configuration state. The runner writes
those files unmodified to<run>/issue-reports/(no envelope: Paramify's
intake parser reads the vendor's own structure), records identity in a sidecar
index, and a new uploader (paramify issues upload) posts each file to
POST /assessment/{assessmentId}/intake. The assessment is chosen by name
(paramify assessments list/select), the same wayparamify programs targetpicks a program UUID without anyone typing one. Start from
fetchers/_template_issue_report/and
docs/issue_report_fetchers.md. Written
against Paramify REST API v0 spec 0.6.0
(API documentation).
Changed
-
A failed fetcher now tells you why, in every category. Paramify used to
show whoever was triagingEncountered 3 API failures during collection— a
count, with no hint of which call broke. Two causes, one root: 103 fetchers
across seven categories (aws80,okta8,knowbe44,k8s3,
paramify3,rippling3,checkov2) never wrote$FETCHER_STATUS_FILE,
so the runner fell back to the tail of stderr andmetadata.errorcarried
whatever the fetcher happened to log last; and the detail was being collected
and then thrown away — every AWS fetcher recorded its causes into a temp
failure log whose only reader, across all 80, waswc -l, and the exit trap
deleted it.metadata.errornow carries the actual cause andmetadata.error_codeone of
seven closed values (auth_failed,not_authorized,target_unreachable,
rate_limited,bad_config,partial_failure,internal_error). Forcing a
credential failure onaws_guard_duty_findingsreported
Encountered 2 API failures during collectionbefore. It now reports:{ "error": "2 AWS API failure(s); first: aws sts get-caller-identity failed;aws guardduty list-detectors failed (exit=254): aws: [ERROR]: An error occurred (UnrecognizedClientException) when calling the ListDetectors operation: The security token included in the request is invalid.", "code": "partial_failure" }228 non-zero exit paths across the tree, 0 unreported. Note that the AWS
family reportspartial_failureuniformly — it counts and quotes the calls
that broke rather than classifying why. Distinguishingauth_failedfrom
rate_limitedfromnot_authorizedthere needs per-call classification and is
follow-up work; the reason text is what carries that information today.For fetcher authors this is a contract change. Reporting a failure is no
longer something you hand-roll: there is now exactly one helper per runtime —
fetchers/_lib/status.shand
fetchers/_lib/fetcher_status.py, both
namedreport_failure— and
docs/fetcher_contract.mdmandates it. It logs
and reports, so it is the whole failure path; logging inside the helper is
what guarantees the reason is the last line on stderr, which is what the
fallback reads. The same nine-line status write previously existed as 27
copies under two names (report_failureandwrite_status) — 22 in
individual fetchers, three in category-shared modules, and one in each
scaffolding template — because the porting playbook printed those nine lines
and told authors to paste them. The playbook and both templates now import the
helper instead. A category-shared module may
re-export it and must not reimplement it, which is how all 80 AWS fetchers
were wired without a single per-filesourceline.One known regression:
crowdstrikefetchers no longer set
metadata.error_code. They passed their own strings (collection_error, a raw
HTTP status) which the shared helper drops as outside the closed set. The error
text is intact, and mapping those onto the seven real codes is a judgement
call left to its own change. -
Every fetcher's
ksisre-keyed to the FedRAMP Consolidated Rules for 2026.
FedRAMP replaced the numeric Phase One indicators (release 25.05C, 52 of them)
with mnemonic ones (version 2026.07.14.01, 46 of them) —KSI-CNA-01is now
KSI-CNA-RNT. Every id changed, so every mapping in the repo did too. Beyond
the re-key:TPRwas deleted andSCR(Supply Chain Risk) added,INRbecame
Incident Response,SVC-SINabsorbed both in-transit and at-rest encryption,
IAM-APMabsorbed both phishing-resistant MFA and passwordless, immutable
infrastructure moved fromCNAtoCMT, and vulnerability management left
MLAentirely.No action required for existing manifests or evidence sets. Manifests
select fetchers by name (use: <fetcher>) and never referenced KSIs; no
fetcher was renamed.evidence_set.reference_idis unchanged on all 176
fetchers, so the uploader's get-or-create resolves to the same evidence sets —
no duplicates, no re-linking.ksisis not carried in the envelope and is
never transmitted to Paramify; it is repo-side reporting metadata read by
paramify ksi.What does change:
paramify ksioutput, and the KSI ids shown in fetcher
descriptions. Evidence sets created from now on drop the(KSI-XXX-NN)suffix
from their display name — the uploader never renames, so sets that already
exist in a tenant keep the name they were created with.Mappings are now documented as suggested / related rather than assertions
that a fetcher satisfies an indicator on its own. Four indicators are left
uncovered on purpose (SVC-VCM,MLA-ALA,RPL-TRC,SCR-MIT): the evidence
does not exist yet, and an honest gap beats a mapping that has to be defended.
Coverage is 32/36 config-evidenceable indicators. -
Dropped "20x" from our own KSI labels in
paramify ksi, the fetcher
schema, and fetcher descriptions. The reference file now carries the exact
release string, so it is the single source of truth for which release we
measure against. FedRAMP 20x program deliverables (the VER reports) keep the
name.
Deprecated
write_statusinazure_commonandgcp_commonis now an alias for the
sharedreport_failure. The 46 Azure and GCP call sites still using it keep
working and are unchanged here; they log twice as a result (their own
logger.errorplus the helper's), which is cosmetic in the run log and does
not affect what Paramify receives. Renaming those call sites and dropping the
alias is one later pass, because the static conformance scan has to stop
crediting the old name at the same moment the call sites change.
Fixed
-
TUI: arrow keys now work where the layout implies they should.
left/
rightmove along a button row — Textual'sButtonbinds onlyenter, so
every Yes/No and Save/Cancel dialog read as a left-right choice while
answering to neither, leavingtabas the only way across.up/down
typed into a filter box now walk the list it filters without taking focus out
of the box, so you can keep narrowing while you move.Both are bound on the container rather than on the widgets, which is what
keeps them safe: a key bubbles up from whatever has focus, so anything that
already owns it keeps it. AnInput'sleft/rightstill moves its text
cursor; aTree's and anOptionList'sup/downstill move their own.
Affects the confirm, form, and picker dialogs, the catalog filter, and the
Manifest and Paramify action rows. -
MySQL RDS instances were reported as not enforcing TLS even when they were.
rds.force_sslis a PostgreSQL and SQL Server parameter; MySQL uses
require_secure_transport.aws_component_ssl_enforcement_statusand
aws_rds_tls_configurationonly read the former, so every MySQL instance came
back as unenforced regardless of its actual configuration. Both now accept
either parameter, matchingON/1/truecase-insensitively. Contributed
by @jazzl0ver.The evidence payload grows (additively):
rds_tls_configurationgains
require_secure_transport,require_secure_transport_source, and a derived
ssl_enforcedper parameter group, plus two summary counters.ssl_enforced
is the either-parameter roll-up and is the field to standardize on downstream;
force_sslis still emitted alongside it. Any validator matching on the old
shape is worth re-checking. Not yet exercised against a live MySQL instance —
the parameter names follow the AWS documentation. -
aws_fsx_encryption_statusover-counted its own failures. It appended raw
multi-line stderr to the failure log that the failure count is awc -lof, so
a run with 2 failures reported 3. The stderr is now flattened to one line per
failure. -
One unreadable KMS key no longer fails the whole collection.
aws_kms_key_rotationexited non-zero when it could not read a single key's
rotation status — an AWS-managed key such asalias/aws/acm, or a customer key
whose policy scopes out the collecting role. The per-keyAccessDeniedwas
already handled, but every failure landed in one bucket that was checked as
"any failure at all", so one unreadable key out of forty failed a run whose
evidence was otherwise complete.An unreadable key now stays in the payload carrying the reason AWS gave
(rotation_status: "unreadable",collection_error: "AccessDeniedException on GetKeyRotationStatus"), and the run succeeds. This follows the contract's own
guidance that a reader of the evidence file should be able to see the
collection was incomplete; the exit code stays the only failure signal.Two payload changes come with it.
rotation_enabledis nownullfor a key
that could not be read — it wasfalse, which asserted "not rotated" about a
key nobody had read and draggedrotation_percentagedown with it. And that
percentage is now taken over the keys actually read, with
readable_keys/unreadable_keysadded to the summary besidetotal_keys.
A validator keying on the old shape should be checked. This is payload, not
contract, so no bump (docs/versioning.md).Collection still fails where the evidence would be empty or wrong: the caller
identity,kms list-keys, or every key being unreadable — that last guard
is new, so a broken identity cannot report "0 of 0 keys rotated" as a clean
run. Reported by @jazzl0ver (#44, fixed in #46). -
EBS encryption-by-default was reported as disabled on accounts where it is
enabled.aws_block_storage_encryption_statusread the setting with
--output text, which renders the API's boolean asTrue, and compared it to
a lowercase"true"that could never match. Any control keyed on
encryption_enabled_by_defaultread as unimplemented on a correctly
configured account — a false negative in the evidence, not a display quirk.This was the only site with the problem: it is the one place in the repo that
reads a boolean through--query … --output textand compares it to a
lowercase literal. Fixed by @jazzl0ver (#47). -
docs/porting_playbook.mdno longer tells you to derivereference_idfrom
a KSI id. It offeredreference_id: <stable id, e.g. KSI-IAM-01>as the
example, which is precisely the advice that cannot survive a renumbering —
and FedRAMP just renumbered everything.reference_idis the immutable key
the uploader get-or-creates on; changing it orphans the evidence set in every
tenant. -
README's category table listed GitLab as 3 fetchers; it has 4
(significant_change_notificationswas missing). -
paramify programs targetno longer misses the manifest it should edit.
Its-f/--filedefaulted to the literalmanifest.yamlresolved against the
current directory, andread_manifest()reads a missing file as an empty
manifest — so running it from a subdirectory, or passing a bare name whose file
lives undermanifests/, silently found nothing and reported "No manifest
entry takes a program as a target", blaming the contents for a path that
wasn't there. Worse, the write then created a second manifest at that wrong
path. Now: omitting-foffers the discovered manifests as a numbered pick
list (annotated with how many entries take a program, since names carry no
convention to guess from) and uses a lone candidate outright, naming it;-f
accepts a bare name or stem, resolved againstmanifests/and the repo root
so it works from anywhere in the tree; and a-fthat resolves to nothing is
an error that writes no file. Under--json(or any non-tty) several
candidates is an error listing them rather than a guess. Othermanifest
subcommands still default to./manifest.yaml.
Full changelog: v0.4.0-beta...v0.5.0-beta
Install by checking out the tag — a plain clone tracks moving main:
git clone https://github.com/paramify/paramify-fetchers.git
cd paramify-fetchers && git checkout v0.5.0-beta
python -m venv .venv && source .venv/bin/activate
pip install -e '.[all]' # or `pip install -e .` for the headless CLI onlyAzure and GCP need their own extras (.[azure], .[gcp]) — see the README.