Skip to content

v0.5.0-beta

Pre-release
Pre-release

Choose a tag to compare

@21tmccauley 21tmccauley released this 02 Sep 16:42
· 803 commits to main since this release
2c0e454

Added

  • 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-After contract on 429s. Commercial and GovCloud both supported —
    cloud_region selects a commercial region or us-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.py derives the request/response shapes,
    tools/crowdstrike_schema_check.py fails
    when a fetcher reads a field the published model does not define,
    tools/crowdstrike_usage_check.py fails when a declared API scope isn't
    exercised (and the reverse), and examples/crowdstrike_mock_run.yaml runs 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 by tools/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 way paramify programs target picks 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 triaging Encountered 3 API failures during collection — a
    count, with no hint of which call broke. Two causes, one root: 103 fetchers
    across seven categories
    (aws 80, okta 8, knowbe4 4, k8s 3,
    paramify 3, rippling 3, checkov 2) never wrote $FETCHER_STATUS_FILE,
    so the runner fell back to the tail of stderr and metadata.error carried
    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, was wc -l, and the exit trap
    deleted it.

    metadata.error now carries the actual cause and metadata.error_code one of
    seven closed values (auth_failed, not_authorized, target_unreachable,
    rate_limited, bad_config, partial_failure, internal_error). Forcing a
    credential failure on aws_guard_duty_findings reported
    Encountered 2 API failures during collection before. 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 reports partial_failure uniformly — it counts and quotes the calls
    that broke rather than classifying why. Distinguishing auth_failed from
    rate_limited from not_authorized there 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.sh and
    fetchers/_lib/fetcher_status.py, both
    named report_failure — and
    docs/fetcher_contract.md mandates 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_failure and write_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-file source line.

    One known regression: crowdstrike fetchers 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 ksis re-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-01 is now
    KSI-CNA-RNT. Every id changed, so every mapping in the repo did too. Beyond
    the re-key: TPR was deleted and SCR (Supply Chain Risk) added, INR became
    Incident Response, SVC-SIN absorbed both in-transit and at-rest encryption,
    IAM-APM absorbed both phishing-resistant MFA and passwordless, immutable
    infrastructure moved from CNA to CMT, and vulnerability management left
    MLA entirely.

    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_id is unchanged on all 176
    fetchers, so the uploader's get-or-create resolves to the same evidence sets —
    no duplicates, no re-linking. ksis is 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 ksi output, 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_status in azure_common and gcp_common is now an alias for the
    shared report_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.error plus 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 /
    right move along a button row — Textual's Button binds only enter, so
    every Yes/No and Save/Cancel dialog read as a left-right choice while
    answering to neither, leaving tab as 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. An Input's left / right still moves its text
    cursor; a Tree's and an OptionList's up / down still 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_ssl is a PostgreSQL and SQL Server parameter; MySQL uses
    require_secure_transport. aws_component_ssl_enforcement_status and
    aws_rds_tls_configuration only read the former, so every MySQL instance came
    back as unenforced regardless of its actual configuration. Both now accept
    either parameter, matching ON / 1 / true case-insensitively. Contributed
    by @jazzl0ver.

    The evidence payload grows (additively): rds_tls_configuration gains
    require_secure_transport, require_secure_transport_source, and a derived
    ssl_enforced per parameter group, plus two summary counters. ssl_enforced
    is the either-parameter roll-up and is the field to standardize on downstream;
    force_ssl is 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_status over-counted its own failures. It appended raw
    multi-line stderr to the failure log that the failure count is a wc -l of, 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_rotation exited non-zero when it could not read a single key's
    rotation status — an AWS-managed key such as alias/aws/acm, or a customer key
    whose policy scopes out the collecting role. The per-key AccessDenied was
    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_enabled is now null for a key
    that could not be read — it was false, which asserted "not rotated" about a
    key nobody had read and dragged rotation_percentage down with it. And that
    percentage is now taken over the keys actually read, with
    readable_keys / unreadable_keys added to the summary beside total_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_status read the setting with
    --output text, which renders the API's boolean as True, and compared it to
    a lowercase "true" that could never match. Any control keyed on
    encryption_enabled_by_default read 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 text and compares it to a
    lowercase literal. Fixed by @jazzl0ver (#47).

  • docs/porting_playbook.md no longer tells you to derive reference_id from
    a KSI id.
    It offered reference_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_id is 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_notifications was missing).

  • paramify programs target no longer misses the manifest it should edit.
    Its -f/--file defaulted to the literal manifest.yaml resolved against the
    current directory, and read_manifest() reads a missing file as an empty
    manifest — so running it from a subdirectory, or passing a bare name whose file
    lives under manifests/, 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 -f offers 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 against manifests/ and the repo root
    so it works from anywhere in the tree; and a -f that 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. Other manifest
    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 only

Azure and GCP need their own extras (.[azure], .[gcp]) — see the README.