Skip to content

v1.52.0

Choose a tag to compare

@n-shadloo n-shadloo released this 27 Aug 21:45
· 2 commits to main since this release
v1.52.0
ddb1d65

secure-code-auditor v1.52.0

Two change sets land in one version. The first is the audit closure:
twenty-two commits, one per reference family, that repair what a file-by-file
audit of the whole corpus found — 3,992 insertions and 785 deletions across
twenty-three files. The second is the rebuild that follows it: the reference
corpus returns to the v1.50.0 language line the closure had crossed, and
SKILL.md and the agent cascade are rewritten to the same standard. No file
was added, renamed, or removed, and the frontmatter description is
byte-identical at 1013 characters.

The audit closure

Every reference took one commit, and the changes fall into three kinds.

Examples that taught the defect they warn about are corrected. The
canonical A01 CommentCreateView called check_object_permissions with
permission_classes = [IsAuthenticated]BasePermission.has_object_permission
returns True, so the call authorized nothing; it now carries a permission
class that implements the object hook. The A04 wrapped-hasher migration gains
the survey pass that stops on a row it cannot wrap, because the write destroys
the only copy of the legacy digest, and the section now schedules the session
and reset-token invalidation that rewriting every password row causes. The
graphene UserType scope returns the set a caller may read rather than the
one row the caller is, and refuses an unauthenticated principal outright,
because AnonymousUser has no primary key and filter(owner_id=user.id)
compiles to owner_id IS NULL. The A09 service example records a permission
denial outside the transaction the denial rolls back, and the DRF atomic
counter chooses its cache-outage branch instead of letting the error become
a 500.

Decision rules that let real findings through are closed. The
duplicate-route dismissal now compares five things — the permission class,
the decorator, the middleware, the queryset scope, and the serializer — where
it compared three. The resolved-route artifact carries the request.urlconf
and set_urlconf caveat, because a middleware that sets either moves the
whole table. The tenancy ladder separates who authored a tenant value from
whether it is still true, and asks for a re-read of membership on the
request. The SSRF allowlist states how a host matches — equality on the
parsed, lowercased, IDNA form, with the scheme and port pinned and userinfo
rejected. A model field's validators argument no longer closes a sink
finding, because save(), bulk_create(), QuerySet.update(), migrations,
and raw SQL never run it.

Verified mechanics land across every file, each read off a primary source
and dated, most against the Django 6.0.7 or 6.1 source on 27 Aug 2026. The
highlights, by file:

  • A02 — the env-string parsing trap (DEBUG = os.getenv("DEBUG", "False")
    is true; security.W018 catches it and security.W020 does not catch
    [""]), a new section on wildcard entries in ALLOWED_HOSTS and
    CSRF_TRUSTED_ORIGINS, the COOP relaxation scoped to the popup response,
    the computed-salt audit gap, base-uri as the CSP directive whose absence
    undoes the nonce, DKIM selector rotation as a DNS deletion, DMARC and SPF
    record counting, the five suppression kinds, and diffsettings --default
    as the drift instrument whose output is secret material.
  • A03 — the pin as the trust decision (--require-hashes binds the
    artifact to the pin, nothing binds the pin to a person), [build-system] requires and pip install . as the two inputs outside the hashed file,
    --only-binary :all:, PIP_EXTRA_INDEX_URL read from the environment, the
    attest job split so no third-party code runs beside id-token: write, and
    the migration backfill routed through _base_manager so a
    use_in_migrations manager cannot hide rows from the count.
  • A04 — the Argon2 memory budget multiplied by worker concurrency, the
    get_random_string entropy arithmetic, COMPARISON_KEY rotation costing a
    restart rather than a migration, the shared-DEK write budget under NIST SP
    800-38D, the KMS encryption context naming the column as well as the row,
    and the re-encryption pass that filters on the value it read so a
    concurrent write survives.
  • A05 — sources that cross the process boundary without a request, the
    first-party wrapper searched as a sink name, template names as an input
    position, interpreter arguments (-c, -e, a command after a host name)
    that an argument list does not close, env= as a second argument channel,
    format_html passing an already-SafeString argument through unescaped,
    the DomainNameValidator exposure shape, and the CSV renderer in
    DEFAULT_RENDERER_CLASSES turning every endpoint into an export location.
  • A06 — ceilings for public flows with no principal, the
    LimitOffsetPagination offset as a second multiplier, JSON list bodies
    outside DATA_UPLOAD_MAX_NUMBER_FIELDS and the ListSerializer
    max_length answer, writers outside the repository that only a constraint
    reaches, repricing on perform_update(), the provider's operation
    identifier as the only key a callback may resolve a row by, and
    plus-address normalization on notification limits.
  • A07 — recovery resolving the same principal set as login
    (get_users() against the exact column), the bcrypt 72-byte truncation
    against the validators that read the whole string, a mechanism for the
    SP 800-63B-4 forced-change requirement, the breach validator hardened
    (https pinned at load, redirect origin checked, bounded read, one
    fail-open exit), the DRF login CSRF gap (APIView.as_view() is
    csrf_exempt and SessionAuthentication enforces nothing before
    authentication), ModelBackend's dummy hash on a miss and the custom
    backends that drop it, the IPv6 /64 lockout key, django-axes defaults read
    off 8.3.1 (AXES_COOLOFF_TIME = None is a permanent lock), a reset never
    satisfying the second factor, and re-authentication minting a bounded
    artifact rather than a session flag.
  • A08 — the Celery remote-control channel enumerated off 5.6.3
    (shutdown, revoke, pool_restart, and the rest ride broker reach),
    worker_enable_remote_control, a new section binding a webhook event to a
    tenant through the stored connection mapping rather than a payload field,
    and the durable RECEIVED row surviving a lost wake-up.
  • A09 — the query string recorded at the proxy tier above LOGGING,
    sensitive_post_parameters raising TypeError on DRF views, alerts on
    the single decisive event and on silence, the audit alias under
    DATABASE_ROUTERS, group and permission writes as m2m_changed events,
    what a hash chain proves and what it cannot, and the decoy register with
    its expected reader set.
  • A10 — the truthy-coroutine policy check (an async def gate called
    without await grants everyone), negated flag names, the sign bound before
    the balance guard, select_for_update() as a silent no-op on SQLite, the
    anchor-row lock for aggregate invariants, the two ways a declared
    constraint is absent (backend support and NULL columns), IntegrityError
    caught at the write and mapped to 409, UniqueValidator as a message
    rather than enforcement, on_commit in autocommit and robust=True,
    idempotency-key validation, read-path expiry, and the stored response as a
    retained copy; the regex subject measured where the match happens, and
    __regex lookups as a database-side engine no Python cap reaches.
  • DRF — update authorizing the record as stored while serializer.save()
    applies the body after, overridden dispatch()/initial() removing every
    control, every @action keyword argument replacing the viewset's list, the
    GET-default action, scopes that resolve to nothing failing open
    (filter(tenant=None) is IS NULL), BasicAuthentication in the default
    list, cookie-carried tokens needing their own CSRF, ObtainAuthToken
    opting out of project defaults, rest_framework.urls mounting plain Django
    login views, version switch-off by route removal, and bulk_create() on
    the create path.
  • Authorization architecture — a permission class implementing only
    has_object_permission as the worse half of the incomplete-class defect,
    autocomplete lookups scoped on the related ModelAdmin while
    limit_choices_to binds the write, the URLconf audit test reading
    initkwargs so a per-action AllowAny cannot hide, identity-selecting
    fields never writable, the allow-list governing every serializer a route
    can build, scoping applied before the aggregate stage so facet counts do
    not leak, and offboarding targets marked done only from a read-back.
  • Data layer — sequence grants split from SELECT, the migration
    bookkeeping table revoked from the runtime role, BYPASSRLS read from
    pg_roles, SECURITY DEFINER functions pinning search_path, policies on
    partitions, views, and materialized views, tenant context on second
    aliases and after COMMIT pops it, the isolation level proven with SHOW transaction_isolation, retry jitter and a driver-tested classifier, and
    role-level ceilings (CONNECTION LIMIT, idle_in_transaction_session_timeout).
  • Data lifecycle — the tombstone releasing its identifier the moment a
    partial constraint frees it, delete receivers connected in
    AppConfig.ready() so a worker cannot take the fast-delete path, the
    credentials that survive an erasure, crypto-shredding decided at the first
    write, the erasure ledger replayed from a store no restore rewrites, the
    three purge-predicate defects, and masked extracts as pseudonymized data.
  • Deployment — the CDN-fronted origin reachable directly, the
    proxy_set_header inheritance trap with X-Forwarded-Host, X-Real-IP,
    Forwarded, and Client-Cert overwritten, the client-IP function checking
    addresses as well as depth and failing closed off the probe path, the
    world-connectable Gunicorn socket (--umask 007), --proxy-protocol
    acceptance, uvicorn and Daphne forwarded-header behavior, systemd
    environments published over D-Bus (LoadCredential= instead), named COPY
    paths, broker URL schemes, and the unauthenticated Redis rating that does
    not rest on a CVE.
  • Uploads — bidirectional-override filenames, the promotion copy carrying
    the server's verdict headers, a private STORAGES alias isolating nothing
    until its OPTIONS differ, the quarantine prefix unreadable outside the
    verification tier, PENDING caps as the only object-count bound, SHA-256
    over the bytes rather than the ETag, and the ${filename} key that
    downgrades boto3's exact-key condition to a prefix match.
  • GraphQL and non-DRF — the cost rule hardened (negative page arguments
    clamped, variables and absent arguments assumed at the ceiling, fragment
    spreads followed and cycles stopped), error masks keyed on
    original_error, list inputs capped before the loop, the persisted-query
    registry that is loaded and never written, Ninja response= as the field
    allow-list and its CSRF model, and gRPC metadata read as a multimap so the
    authorized credential and the recorded one cannot differ.
  • Service identity — sender-constrained binding as verification step
    eight, the module-level PyJWKClient with an explicit timeout, http JWKS
    URIs accepted by the library, the full DPoP proof checks and the
    fleet-wide jti store, the proxy chain where the verifying hop and
    REMOTE_ADDR must be the same hop, CI federation bound to a subject, and
    introspection as the only thing that shortens a leaked token's life.
  • Privileged access — the invariant split per mechanism (impersonation
    stays below the operator, elevation is bounded by scope and time), the
    credential surface denied to an impersonated session on read and write,
    the server-side episode record as the authority, the audit receiver that
    must raise past 500 to roll the acquire back, hijack_history[0] naming
    the person in a chained acquire, release-rather-than-reject middleware,
    and the target notified out of band after the episode.
  • Library index — the two-clocks dating rule, floors that bind the
    project rather than the integration, SimpleJWT denylisting inert without
    the blacklist app, the django-auth-ldap row with the python-ldap>=3.4.5
    floor, guardian's anonymous-user row as a live grant target,
    django-tenants pinned exactly, django-celery-beat's PeriodicTask as a
    task-execution control plane, and the DRF 3.17.2/3.18.0 security-against-
    feature split.

The language restoration

The closure added 74 prose sentences over the 25-word ceiling the v1.50.0
language pass set. Each one is split back under it, with the meaning
unchanged — no control, threshold, identifier, or date moved. The verbatim
SP 800-63B-4 blocklist requirement in the authentication file stays the one
standing exception, kept intact because a split would falsify a quoted
normative SHALL. The methodology file also drops a stale count: its ASVS
renumbering note said twenty-three files where the corpus holds twenty-five,
and the claim is now count-free.

The router rebuild

SKILL.md is rewritten to the corpus's own ASD-STE100 standard. The router
rows become keyword lists — the same trigger vocabulary, without the run-on
sentences the language pass never reached — and the file falls from 39,065 to
32,436 of the 40,960 bytes the docs-integrity workflow allows, restoring the
headroom the contribution guide names as a standing constraint. The ownership
table keeps every row and the three prose splits (path traversal,
configuration against runtime, human against machine identity). The
twenty-two-references count becomes a count-free statement, so the next
reference added cannot make it stale. Mode selection, the scripts contract,
the proof section, the stop conditions, the severity summary, and the
freshness statement all stand, tightened to the sentence rules.

AGENTS.md, GEMINI.md, and the .cursor rule become the thin pointers
they always claimed to be. Each one now states the load order, the two modes,
and the script contract, and defers the per-file routing to the SKILL.md
router — so the router has one home, and the pointers cannot drift from it.
The four issue templates and the README changelog carry the new version.

Verified on the finished tree

docs-integrity passes with 25 reference files, no orphan, balanced fences,
and SKILL.md at 32,436 of 40,960 bytes. validate-skill passes with the
description byte-identical at 1013 characters. The dangerous_patterns.py
self-test passes 49 fixtures with 29 of 29 rules covered, all three scanners
exit 0 with a kind: "summary" record on a missing path, and they report
zero findings against this repository. Every file-and-heading cross-reference
in the corpus was re-verified — 366 pairs resolve, four two-heading
continuations included. The library-index date does not move: no PyPI sweep
ran this release.