BUG BOUNTYFIELD MANUAL
Target playbooks
Markdown
0 / 145 checkedChecks last until this page reloads.

Cloud, containers, Kubernetes and infrastructure as code

The hunting unit is a principal acting on a resource across a promised boundary. A cloud-hosted application, its customer's cloud account, the underlying managed service, and the cloud provider are separate scopes. A public hostname or uploaded domain inventory does not establish ownership of the backing bucket, account, cluster, CDN distribution or SaaS tenant.

Investigation modules#

Use these procedures with this playbook's setup and scope. Each contains the full testing sequence, command cheat sheet, interpretation, and checklist; open its canonical page for cross-target references.

Object, function, and property authorizationTurn identities, tenants, objects, roles, and lifecycle transitions into a reproducible permission matrix.

Open canonical module and reverse references

Investigation sequence
  1. 01Establish permission rule
  2. 02Seed two controlled identities
  3. 03Replay one changed dimension
  4. 04Verify durable effect
  5. 05Revoke and repeat
  6. 06Record smallest violation

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and invariant

Use accounts A and B, two private tenants, and one synthetic document per tenant. Create a viewer role only if the product supports it. Record the exact build, API version, actor, tenant membership, document visibility, and expected permission rule. The invariant is: the server authorizes the actor, action, object, and each protected property together. UI visibility is supporting context; product policy establishes the boundary. Horizontal, vertical, and context-dependent authorization are separate dimensions. Access-control models.

Numbered test procedure

  1. Capture a working baseline. As A, create private O1 containing BB-A-ONLY-01; as B create O2 containing BB-B-ONLY-01. Save each normal read response and a normal reversible rename. Keep separate browser profiles and credentials. Confirm anonymous access to O1 fails.
  2. Test the object dimension. Replay A's O1 read using B's credentials, changing nothing else. Then keep B's identity and replace O1 with O2 as a positive control. Substitute only known controlled identifiers, including parent tenant IDs, child IDs, attachment IDs, and export-job IDs. A nonexistent synthetic ID is the negative control for error handling; it does not replace the cross-account test.
  3. Test the function dimension. Give B a documented viewer role in A's test tenant. Capture an owner-only operation as A, such as renaming a private test folder, and replay it as B. If the product permits viewer renames, select an actually restricted operation. Inspect durable state from A's browser after the request completes.
  4. Test the property dimension. Start with a permitted profile update. Add exactly one known server-controlled property from schema or source, such as a role field in the isolated tenant. Read back the stored object, then test one corresponding restricted action. Repeat separately for unauthorized response properties. Automatic binding of request fields can create mass-assignment issues; read-side property disclosure is a different mechanism. OWASP mass assignment.
  5. Test lifecycle and alternate delivery. Remove B's membership, replay the previously successful request, and check an existing subscription or queued export. Repeat the same permission rule at list/search, download, batch, and versioned endpoints where present. Use two-item batches containing O1 and O2; do not enumerate identifiers.
  6. Interpret and clean up. Match markers, object IDs, committed changes, and resulting permissions. A 200 with an empty list, a 202 for a rejected job, or a redirect without private content is not proof. Remove synthetic records and memberships after preserving minimal evidence.

Command card

These illustrative routes require your local replica. Create A.header and B.header containing each test account's complete Authorization: Bearer … header; protect the files and exclude them from reports. curl can load headers from a file. curl manual.

mkdir -p evidence/authz
chmod 600 A.header B.header
curl -q --noproxy '*' --max-time 10 --header @A.header \
  -D evidence/authz/a.h -o evidence/authz/a.json \
  http://127.0.0.1:8000/api/documents/O1
curl -q --noproxy '*' --max-time 10 --header @B.header \
  -D evidence/authz/b.h -o evidence/authz/b.json \
  http://127.0.0.1:8000/api/documents/O1
curl -q --noproxy '*' --max-time 10 --header @B.header \
  -o evidence/authz/control.json \
  http://127.0.0.1:8000/api/documents/O2

Report threshold and checklist

Report a demonstrated unauthorized read, action, or protected-property change. Attach the intended rule, both actors' roles, minimal request difference, and independent state verification. UUID disclosure, shared objects, intentionally public exports, stale UI state, and identical status codes are common false positives.

  • Ownership and visibility confirmed before testing.
  • Object, function, and property decisions tested separately.
  • Positive and negative controls captured.
  • Background output and revoked membership checked.
  • No unrelated customer records accessed.

Local curl examples use --noproxy '*' to bypass inherited proxy variables; -q alone only disables the default curl configuration file. Configure intentional interception separately. curl manual.

Authentication, recovery, federation, and session lifecycleTest registration, recovery, identity changes, invitations, SSO enforcement, and deprovisioning as one account state machine.

Open canonical module and reverse references

Investigation sequence
  1. 01Map identity states
  2. 02Capture valid and invalid controls
  3. 03Alter one binding
  4. 04Verify account identity
  5. 05Test revocation
  6. 06Restore account settings

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and invariant

Prepare two controlled accounts, separate inboxes, browser profiles, and a recovery route for each. Use an isolated identity provider for federation modifications. Write the accepted identity states: unverified, password accepted, MFA pending, fully authenticated, elevated, recovered, and revoked. The invariant is: each privileged transition requires the right identity proof, and credentials remain bound to their intended account and purpose. Consult documented expiry and revocation semantics before judging residual access. OWASP authentication.

Numbered test procedure

  1. Inventory entry points. Record registration, email verification, password login, passkey/MFA completion, mobile login, SSO, API-token issuance, email change, linking, reset, and logout. Capture one complete normal sequence with timestamps and account identifiers. An anonymous call to /api/me and an intentionally invalid credential form negative controls.
  2. Check incomplete authentication. Stop A's normal login immediately before MFA. Use that intermediate cookie or token for a single protected profile read. Compare it with a completed A session and an anonymous request. Also check the observed alternate API route; avoid guessing or spraying credentials. Protected access without the required second factor is the finding, not the mere existence of an intermediate token.
  3. Check recovery binding. Request one reset for A and one for B. In the local replica, submit A's reset proof with B's account selector, if that selector is client-controlled. After an authorized normal reset of A, replay the consumed proof once. Confirm which controlled account's credential changed by a fresh login. Restore passwords afterward. Single-use, expiry, and account binding are independent checks. OWASP recovery.
  4. Check session transitions. Save the pre-login identifier, completed session, and any refresh credential separately. Compare identifiers after login and elevation. Perform logout or explicit device revocation and make one old-session profile request. Record access and refresh outcomes separately; a refresh token being revoked does not necessarily revoke already-issued access tokens. Evaluate the actual advertised security behavior. OWASP sessions.
  5. Check identity changes and pre-existing state. Change A's verified email to a second owned inbox; compare verification, pending state, and recovery delivery before/after confirmation. In the replica, keep an unverified password account open, then complete the legitimate verified/federated signup for the same owned inbox in another profile. Inspect whether the original password, session, or linked identity can still access the now-verified account. Independently test reusing a stale verification link after another email change. A duplicate signup error or two aliases reaching the same mailbox proves no takeover. Do not assume case folding, dot removal, or plus-address handling is universal. Email identity comparison and verification.
  6. Follow enterprise onboarding and offboarding. Use a disposable tenant with a second recovery administrator. Record invitation recipient/role/tenant, acceptance, cancellation, SSO enforcement, group assignment, and deactivation. Compare a fresh invite with one cancelled invite; confirm whether a changed recipient is allowed by the product contract. After enabling enforced SSO, attempt the observed password, reset/magic-link, and alternate client entry paths once with controlled accounts, then request one protected tenant object. Deliberate break-glass exceptions are separate policy. For SCIM, use the tenant's provisioning credentials and verify deactivation, group removal, token refresh, and existing streams after the configured sync interval. SCIM active has provider-defined semantics; it is not a universal immediate-session-kill instruction. SCIM schema. Provisioning credentials must still map to the permitted tenant/resources. SCIM authorization.
  7. Follow the specialized proof branches. Use MFA/passkey lifecycle for enrollment, factor replacement, backup-code reuse, and WebAuthn verifier checks. Use federation/token trust for OAuth PKCE, OIDC claim and account-linking decisions, JWT validation, and SAML. Do not substitute decoded claims for a protected-resource result. Document which providers, authentication methods, and enterprise features were unavailable rather than marking them passed.

Command card

Export A's local cookie jar before using the application's real logout button. These requests compare protected content before and after logout; routes and credential transport must match the replica.

mkdir -p evidence/session
chmod 600 A-before.cookies
curl -q --noproxy '*' --max-time 10 -b A-before.cookies \
  -D evidence/session/before.h -o evidence/session/before.json \
  http://127.0.0.1:8000/api/me
# Perform the actual logout or device-revocation workflow now.
curl -q --noproxy '*' --max-time 10 -b A-before.cookies \
  -D evidence/session/after.h -o evidence/session/after.json \
  http://127.0.0.1:8000/api/me
curl -q --noproxy '*' --max-time 10 -o evidence/session/anonymous.json \
  http://127.0.0.1:8000/api/me

Report threshold and checklist

Report access as the wrong controlled identity, completion without required authentication, unauthorized account linking, or a material violation of revocation guarantees. Cosmetic logout, unsent browser cookies, decoded JWT contents, and reset forms displaying without completing are common false positives.

  • Account and session state recorded for every request.
  • Browser binding distinguished from token validity.
  • Correct and incorrect proofs compared independently.
  • Fresh login verifies any credential or linking change.
  • Verification, linking, invitations, and enforced-SSO fallback paths evaluated.
  • Provisioning changes checked against configured sync and revocation behavior.
  • Recovery settings restored; live tokens removed from report.
Trace input into queries, commands, and templatesProve interpretation at a specific sink using small paired experiments instead of payload spraying.

Open canonical module and reverse references

Investigation sequence
  1. 01Identify input and sink
  2. 02Freeze a synthetic fixture
  3. 03Send paired controls
  4. 04Observe sink semantics
  5. 05Confirm smallest effect
  6. 06Document root cause

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and invariant

Use a local replica seeded only with synthetic records, a normal request, and an observable sink: database query logs, a template render, or a disposable worker's process log. Identify input type and decoding layers before selecting syntax. The invariant is: untrusted input remains data, or is constrained to an explicitly authorized language. A SQL search field, MongoDB filter, template editor, and process argument have different trust contracts; the same string is not a universal test.

Numbered test procedure

  1. Trace the route. From the handler, follow validation, normalization, service calls, query builders, template compilation, and subprocess invocation. In a black-box target, infer this from observed feature behavior and errors, then reproduce locally when possible. Record the last point where input is still data and the first interpreter boundary. An ORM can still contain unsafe raw fragments.
  2. Freeze controls. Create exactly one matching synthetic record and one nonmatching record. Capture the normal response. Choose an input that must produce no match under the intended semantics. Keep actor, tenant, pagination, and cache state unchanged across each pair.
  3. Test SQL or NoSQL semantics. For a known SQL string context in the lab, compare logically true and false expressions while retaining the same synthetic predicate. Prefer local query logs over timing or data extraction. For a JSON filter expected to accept a string, compare {"name":"BB-CANARY"} with {"name":{"$eq":"BB-CANARY"}}, then $eq with a nonexistent marker. Operator acceptance becomes a vulnerability when it escapes the allowed query contract or security predicate; intentional query APIs are different. SQL injection, NoSQL injection.
  4. Test template evaluation. For a documented Jinja/Twig-like local sink, compare literal text, {{7*7}}, and {{7*8}}. Inspect the raw HTTP response before JavaScript executes. Results 49 and 56 support server interpretation; a single 49 might be ordinary content. If the feature intentionally supports expressions, determine whether a lower-trust actor crosses a promised sandbox or data boundary before reporting. Arithmetic alone does not establish OS execution. Template injection.
  5. Test command interpretation locally. With an identified POSIX shell sink, use a disposable process and a marker-only operation such as printf BB-CMD-01; compare the literal argument path with the shell-interpreted path in process logs. Avoid file writes, network callbacks, account enumeration, and long sleeps. Also distinguish argument injection into a legitimate executable from starting an additional shell command. Command injection.
  6. Confirm causality and clean up. Repeat the baseline and one successful pair, then switch to the validated/parameterized route in the replica as a negative control. Capture the input-to-sink path, raw values after decoding, and smallest observable impact. Remove fixtures and stop at proof.

Command card

The following performs three bounded template probes against an illustrative local endpoint. Use only when that endpoint is expected to render the parameter as text; do not infer engine identity from one expression.

mkdir -p evidence/injection
curl -q --noproxy '*' --max-time 10 --get --data-urlencode 'name=BB-LITERAL' \
  -o evidence/injection/baseline.txt \
  http://127.0.0.1:8000/preview
curl -q --noproxy '*' --max-time 10 --get --data-urlencode 'name={{7*7}}' \
  -o evidence/injection/expression-a.txt \
  http://127.0.0.1:8000/preview
curl -q --noproxy '*' --max-time 10 --get --data-urlencode 'name={{7*8}}' \
  -o evidence/injection/expression-b.txt \
  http://127.0.0.1:8000/preview

Report threshold and checklist

Report attacker-controlled interpretation that violates the feature's contract, supported by paired results or an instrumented source trace. Syntax errors, WAF blocks, reflection, slow requests, and intentionally supported expression syntax are insufficient alone. State whether the demonstrated impact is query manipulation, expression evaluation, argument injection, or command execution.

  • Sink, language, quoting context, and decode order identified.
  • Synthetic positive and negative records available.
  • One input changed per comparison.
  • Raw server output separated from client rendering.
  • Evidence stops short of secret retrieval or destructive execution.

Local curl examples bypass environment proxies with --noproxy '*'; -q alone only suppresses curl configuration files. These snippets target loopback and do not follow redirects. curl manual.

Delayed consumers and argument-only execution

A safe database insert does not make the stored value trusted forever. In the replica, save a marker through the normal create/update route, then trigger one observed export, search, scheduled job, or administration view that consumes it. Capture both requests, the stored bytes, and the later interpreter call. Repeat with a literal marker and one context-specific expression; keep the actor and selected synthetic record constant. This checks second-order injection that an immediate-response scan misses. Second-order SQL injection.

Treat process arguments as a separate boundary. A subprocess argument array with shell=False can still pass an attacker-controlled option to the intended executable. In a local wrapper, compare a normal filename-like argument with a harmless, documented mode switch and observe the exact argument vector and output. Prove that the caller was allowed to supply a value but not that mode. Do not test options that write files, run another program, or contact a network. Report shell-command interpretation under CWE-78 and argument/option injection under CWE-88; neither automatically proves the other. CWE-88.

  • Stored input followed through its later privileged consumer where one exists.
  • Argument vector distinguished from shell command text.
Server-side fetching and network trust boundariesSeparate an intended fetch from a request that reaches a prohibited destination, forwards authority, or exposes a protected response.

Open canonical module and reverse references

Investigation sequence
  1. 01Map fetch contract
  2. 02Start controlled canary
  3. 03Confirm ordinary fetch
  4. 04Change one destination rule
  5. 05Correlate server evidence
  6. 06Bound the impact

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and invariant

Identify a feature that causes server-side requests: preview, import, webhook verification, document conversion, image fetch, repository import, or AI tool execution. Record allowed schemes, destinations, redirect rules, credentials, and whether response bodies are returned. Use a local replica with a tiny HTTP canary reachable from its worker. If the application runs in a container, localhost means that container; place the canary on an explicitly controlled test network instead of guessing host addresses.

The invariant is: every actual destination and credential transfer follows the feature's intended policy. A URL-fetching feature contacting your public receiver is often normal functionality. SSRF requires an unintended request or trust-boundary violation. SSRF mechanics.

Numbered test procedure

  1. Document the ordinary path. Submit a permitted static URL on your own receiver, containing a unique marker in its path. Record submission time, job ID, request method, receiver log, worker identity if available, and returned output. Confirm the browser did not fetch the URL directly by repeating through curl with the browser closed.
  2. Establish controls. Submit a second permitted URL that returns a different marker. Also submit a deliberately rejected scheme or destination from the feature's documented policy. Record whether rejection happens before queueing, at DNS resolution, after connection, or during response handling. A DNS lookup alone establishes resolution, not HTTP access.
  3. Test one policy boundary in the replica. Compare a permitted receiver with an owner-provided private canary that policy should block. If arbitrary public destinations are intended, concentrate on restricted network ranges, forbidden ports or schemes, or unintended credentials, using only designated canaries. Never use production metadata credentials as proof.
  4. Test redirect handling. Configure your first controlled endpoint to return one redirect to the second controlled canary. First verify both endpoints independently. Submit the first URL and correlate logs to show whether the second destination was validated. An initial allowlist check is insufficient if the actual redirected destination crosses the rule. Where authorized, study DNS validation/connection consistency in the replica with a controlled resolver; do not perform Internet-wide rebinding experiments. OWASP SSRF prevention.
  5. Inspect authority and output. Check whether a synthetic authorization header, test cookie, or privileged worker identity reaches the receiver unexpectedly. If the app displays a response, return only a fixed canary string. Distinguish blind connectivity from a readable internal response, and identify whether headers, method, body, or destination are attacker-controlled.
  6. Interpret and stop. Correlate exact marker, time, source, request, and job. Rule out your browser, webhook delivery by design, scanners, and unfurling services. Stop after the first controlled boundary violation; do not sweep adjacent hosts or ports. Shut down canaries and delete test jobs.

Command card

Run the receiver in a new empty directory in one terminal. This Python server serves only the canary directory and binds loopback. It is a local test receiver, not production hosting. Python HTTP server.

mkdir -p ssrf-canary
printf '%s\n' 'BB-SSRF-CANARY-01' > ssrf-canary/probe.txt
python3 -m http.server 8765 --bind 127.0.0.1 --directory ssrf-canary

In another terminal, send one request to your local application's observed fetch route. The example assumes same-host processes and JSON request data.

curl -q --noproxy '*' --max-time 10 --header @A.header \
  -H 'Content-Type: application/json' \
  --data '{"url":"http://127.0.0.1:8765/probe.txt?case=BB01"}' \
  http://127.0.0.1:8000/api/preview

Report threshold and checklist

Report the prohibited destination or credential boundary actually reached. Label DNS-only evidence and environmental limitations explicitly. An open redirect or successful public image import alone does not prove internal access.

  • Feature's destination contract written down.
  • Receiver ownership and worker network namespace established.
  • Ordinary fetch and rejected control recorded.
  • Redirect destination and credential behavior inspected.
  • No metadata secrets, unrelated internal addresses, or port sweeps used.

Local curl examples bypass environment proxies with --noproxy '*'; -q alone only suppresses curl configuration files. These snippets target loopback and do not follow redirects. curl manual.

Resolver, retry and connection identity

In source review, trace the URL parser, A/AAAA lookup, selected socket address, redirect handling and retry path as one decision. Verify the checked address is the one actually connected to; a second resolution, IPv6 path, alternate IP representation, redirect or worker retry must not escape the intended policy. For a DNS-change hypothesis, use a controlled resolver and designated canaries in the replica, record returned addresses and actual connections, and compare with a fixed-answer control. Merely accepting an unusual URL spelling does not prove a prohibited connection. Keep TLS verification enabled where HTTPS is used; do not change the client trust model to make the proof succeed. OWASP SSRF prevention.

Uploads, downloads, paths, archives, and XMLFollow a file through acceptance, storage, processing, publication, and retrieval while preserving ownership and path boundaries.

Open canonical module and reverse references

Investigation sequence
  1. 01Map file lifecycle
  2. 02Seed harmless canaries
  3. 03Change one file property
  4. 04Check parser and destination
  5. 05Verify another controlled reader
  6. 06Remove every derivative

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and invariant

Use a local replica, a tiny supported file, two controlled accounts, and a dedicated scratch directory. Identify upload handler, object store, converter, background worker, CDN/download route, and deletion path. Place a unique harmless canary just outside the permitted download root, through normal local filesystem access. The invariant is: user-controlled file metadata or contents cannot escape storage, interpretation, or ownership rules.

Separate filename, MIME declaration, actual bytes, storage key, filesystem path, and retrieval origin. Each component may normalize them differently. Acceptance of an unusual extension alone does not prove unsafe execution. Upload handling.

Numbered test procedure

  1. Record a supported baseline. Upload a tiny text/image fixture through the documented route. Save its object ID, resulting filename, declared and detected type, processor status, retrieval headers, and response bytes. Verify A can retrieve it and B cannot when the file is private.
  2. Change one property. Resubmit the same harmless content with a different filename or MIME declaration, independently. For a type-restricted image endpoint, text labeled image/png should either fail or be safely handled. Inspect the stored result and actual browser rendering. Do not upload executable shells or server configuration files to demonstrate an extension mismatch.
  3. Check path containment. In the local replica, request the planted out-of-root marker using one relevant ../ path. Compare a valid in-root file and a nonexistent sibling path. Preserve the raw request target and server-resolved path; a client or proxy may normalize dot segments before they reach the application. If testing writes, use only a dedicated disposable output directory and check that an outside marker is not created. Path traversal.
  4. Check ownership through derivatives. As B, request A's controlled original, thumbnail, conversion result, archive export, and any observed signed URL. Record whether the URL is intentionally a share capability and its documented lifetime. Deleting the primary record does not prove every derivative became inaccessible; evaluate the product's promised behavior.
  5. Check archive and XML processing locally. For an extractor, create a tiny archive with one ordinary member and one canary path outside its designated extraction directory; inspect resolved paths without overwriting anything important. For an XML parser, compare a plain literal field with one external entity referencing the controlled HTTP canary from the SSRF module. Correlate callback logs and returned marker. Do not use recursive entities, oversized documents, or operating-system files. XML acceptance or a DOCTYPE error alone is not external-entity resolution. XXE.
  6. Interpret downstream effects. Wait for the single test job to finish. Compare bytes, filesystem location, ownership, browser origin, and any parser callback. Source review should locate the exact join/extraction/parse operation and whether containment checks occur after normalization. Delete originals, derivatives, exports, and queued test jobs.

Command card

This creates a local download fixture. Configure the replica's allowed download directory as file-lab/public; illustrative endpoint and parameter names must match the application.

mkdir -p file-lab/public evidence/files
printf '%s\n' 'BB-INSIDE-01' > file-lab/public/inside.txt
printf '%s\n' 'BB-OUTSIDE-01' > file-lab/outside.txt
curl -q --noproxy '*' --max-time 10 --get --header @A.header \
  --data-urlencode 'name=inside.txt' \
  -o evidence/files/baseline.txt \
  http://127.0.0.1:8000/api/download
curl -q --noproxy '*' --max-time 10 --get --header @A.header \
  --data-urlencode 'name=../outside.txt' \
  -o evidence/files/traversal.txt \
  http://127.0.0.1:8000/api/download

Report threshold and checklist

Report a proven outside-root read/write, unauthorized controlled-file access, unsafe browser/server interpretation, or prohibited parser fetch. A reflected filename, harmless stored extension, public sharing by design, and asynchronous rejection are frequent false positives.

  • Upload and retrieval rules documented independently.
  • Only tiny, synthetic files and dedicated canaries used.
  • Filename, MIME, contents, and ownership varied separately.
  • Raw request and resolved destination retained.
  • Processed derivatives included in evidence and cleanup.

Local curl examples bypass environment proxies with --noproxy '*'; -q alone only suppresses curl configuration files. These snippets target loopback and do not follow redirects. curl manual.

Containment beyond dot segments

For a link-capable filesystem or archive format, test a tiny symbolic-link fixture in the disposable directory as a separate case. Point it only to the already-planted harmless outside canary, then exercise the product's real reader/extractor. Record the final resolved destination and the identity that opened it. A lexical .. rejection does not establish containment when links, absolute member paths, platform separators, or case collisions change the destination. Do not generalize a local administrator-created link into a remote vulnerability: show how the stated attacker can create or influence that link through the product.

Pin the runtime and actual extraction API/filter. Python's tar extraction filters can reject outside destinations and links, but filter availability, explicit overrides, and additional resource limits matter. A failed extraction may also leave earlier members on disk; inspect and remove only the disposable output. If a TOCTOU claim depends on replacing a path between validation and opening, demonstrate that timing separately in the replica; a static link test does not prove a race. Python extraction filters and limitations.

  • Final resolved file or link target stays inside the intended root.
  • Actual runtime/filter and partial-extraction cleanup documented.
Workflow invariants, replay, idempotency, and racesFind security consequences in workflow ordering and concurrency, with serial controls and durable-state evidence.

Open canonical module and reverse references

Investigation sequence
  1. 01Write business invariant
  2. 02Capture normal transitions
  3. 03Change ordering or replay
  4. 04Compare serial control
  5. 05Try bounded lab concurrency
  6. 06Verify committed state

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and invariant

Choose one reversible workflow in a local or nonfinancial sandbox: accepting an invitation, creating one test resource, applying a test credit, approving a draft, or consuming a one-time action. Write the exact invariant, for example: one invitation grants at most one membership, to its intended recipient and role. Record preconditions, state transitions, actor permissions, durable result, retries, and cleanup. Logic findings depend on intended rules, not whether the request is technically valid. Business logic.

Numbered test procedure

  1. Draw the normal transition table. List draft → submitted → approved → completed, or the product's actual equivalent. For each edge, record actor, required prior state, immutable fields, and transaction identifier. Perform one normal run with synthetic objects and save before/after state, not just HTTP responses.
  2. Test skipped and reordered transitions. On a fresh disposable object, attempt the observed completion request before approval. Then change only a value that should be frozen after approval, such as recipient or test quantity. Compare with a fully approved control and an invalid object ID. If a transition is intentionally optional, adjust the invariant rather than treating missing UI steps as a bug.
  3. Test serial replay. Repeat the same completed operation once after it settles. For documented idempotency keys, compare the same key/same body with the same key/different body, using sandbox data. Determine whether keys are scoped by actor, tenant, endpoint, and expiry. Returning the first result again can be correct idempotent behavior.
  4. Test boundary values selectively. Use zero, a single negative value, or a precision boundary only where the sandbox permits it. Record server-calculated totals and immutable values. Avoid real billing, inventory, refunds, transfers, or customer notifications. A strange displayed number without a committed consequence is not enough.
  5. Test concurrency only in an authorized setting. First demonstrate expected serial behavior. Reset a test object, then send exactly two simultaneous requests as a small experiment. The local example below uses a barrier; it does not guarantee server arrival within the vulnerable interval. If there is no overlap, record inconclusive timing rather than absence of a race. More aggressive synchronization belongs in the isolated lab. Race conditions.
  6. Inspect after completion. Wait for both requests and relevant workers. Count durable memberships/resources or inspect the ledger of synthetic events. Two success responses may correspond to one commit. Repeat only to resolve a specific uncertainty, then restore the object and stop at the first demonstrated invariant failure.

Command card

This sends exactly two requests to a disposable local invitation endpoint. Put a valid local test bearer token in A.token, with restrictive permissions, and replace the route with the observed one. Create a fresh invitation for each serial/concurrent comparison. Run against localhost only.

from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from threading import Barrier
from urllib.request import Request, build_opener, ProxyHandler, HTTPRedirectHandler
from urllib.error import HTTPError

class NoRedirect(HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None

gate = Barrier(2)
token = Path('A.token').read_text().strip()
def attempt(index):
    req = Request('http://127.0.0.1:8000/api/invitations/TEST-INVITE/accept',
                  data=b'{}', method='POST',
                  headers={'Authorization': 'Bearer ' + token,
                           'Content-Type': 'application/json'})
    gate.wait()
    try:
        with build_opener(ProxyHandler({}), NoRedirect()).open(req, timeout=10) as response:
            return index, response.status, response.read(1024).decode(errors='replace')
    except HTTPError as error:
        return index, error.code, error.read(1024).decode(errors='replace')

with ThreadPoolExecutor(max_workers=2) as pool:
    print(list(pool.map(attempt, range(2))))

Report threshold and checklist

Report a durable violation of the stated rule, with serial controls, initial conditions, minimal request sequence, and final state. Duplicate responses, transient UI counters, expected retries, and intentionally cumulative operations are common false positives. Describe the demonstrated consequence; do not infer financial loss from a sandbox counter alone.

  • Business rule and expected actor written down.
  • Normal and serial-replay controls completed.
  • Fresh fixture used for each experiment.
  • Only bounded authorized concurrency used.
  • Durable state and asynchronous effects verified.

The Python example disables environment proxies and redirect following. A redirect is captured by the HTTPError branch; inspect its destination before any separate request.

Events that arrive after the request

Apply the same invariant to workers and integration events, not only the HTTP endpoint. Record a deduplication key's scope and when it becomes durable relative to the protected effect. A claim written before a worker fails can lose work; a claim written after the effect can permit duplicates. Use an instrumented fault point in the replica to distinguish these cases, and inspect committed state after retry. Then run the webhook event integrity procedure for signature, event ownership and out-of-order delivery checks. Do not assume provider delivery time is a reliable object version or that an HTTP acknowledgement proves processing completed.

REST, GraphQL, gRPC, WebSocket, and SSE permission parityCarry the same permission invariant across protocols, nested fields, streams, and alternate API versions.

Open canonical module and reverse references

Investigation sequence
  1. 01Inventory observed operations
  2. 02Choose one permission invariant
  3. 03Compare controlled identities
  4. 04Test alternate protocol path
  5. 05Check stream lifecycle
  6. 06Record semantic result

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and invariant

Start from normal client traffic and supplied API schemas. Create two controlled accounts and private synthetic objects. Map REST paths, GraphQL operations/fields, gRPC methods, and WebSocket message types to the same business action. The invariant is: changing transport, API version, batching, or connection lifetime does not widen an actor's permission. Reuse the authorization matrix and session modules; protocol-specific testing adds delivery details rather than duplicating those foundations.

Numbered test procedure

  1. Build a semantic inventory. Record operation name, method/path, actor, tenant selector, object ID, request schema, response fields, and side effects. Observe one success and one forbidden controlled-object request per available protocol. Preserve response bodies, GraphQL errors, gRPC status/trailers, or socket messages; HTTP status alone does not define application success.

  2. Check REST parity. Replay the same controlled-object rule through observed v1/v2 routes, nested resources, exports, and a two-item batch. Compare read, update, and server-controlled properties independently. An alternate route being live is not a vulnerability unless its behavior violates the intended permission or lifecycle rule.

  3. Check GraphQL resolvers and fields. Begin with the client's existing query. As B, change only A's known object ID, then request one protected field omitted from B's normal response. Try the observed global-node route if present. Use at most two aliases as a bounded comparison of resolver behavior; do not use recursive depth or massive aliases. A schema or introspection response helps discovery but is not a confidentiality breach by itself. Where cookie authentication and alternate HTTP methods exist, check a reversible mutation through the observed transport: GraphQL GET is for queries, not mutations. If claiming CSRF, prove delivery in a real browser using the browser-trust procedure. Persisted query IDs are operation selectors, not permission proofs. GraphQL HTTP semantics. GraphQL testing.

  4. Check gRPC context. Use the provided .proto file or authorized reflection to identify one method. Compare channel identity, per-call credentials, tenant metadata, and the selected resource. Repeat a forbidden request with B's genuine credentials, not a token fabricated from decoded claims. For streaming methods, check whether a permitted start improperly authorizes later messages for another controlled resource. Channel credentials and call credentials serve different roles. If an HTTP/JSON transcoding gateway or gRPC-Web route is actually deployed, replay this same invariant there: a translated request can travel through different authentication middleware. Preserve the gateway path and backend method instead of inferring exposure from a .proto annotation. HTTP/JSON transcoding. gRPC authentication.

  5. Check WebSocket and SSE lifecycle. Capture a normal handshake and one subscription message. In B's profile, request A's private room using its known ID; have A send a single harmless marker. Remove B from a previously shared room and test one further message on the existing socket. For cross-site hijacking, use a real controlled origin and actual browser cookies. An accepted forged Origin header from a nonbrowser client does not prove browser delivery. OWASP WebSocket security. For server-sent events, use the observed event endpoint with A and B, then remove B and inspect one new synthetic event. A reconnect cursor or Last-Event-ID selects delivery position; test that it cannot select another controlled tenant's history. Close the browser EventSource during cleanup because it normally reconnects automatically. SSE connection and reconnection.

  6. Interpret and consolidate. Read partial data as well as error arrays; an error on one field can coexist with leaked data elsewhere. Correlate socket subscription IDs and asynchronous messages. Group instances by shared root cause only when the evidence supports it, and name untested transports explicitly. Close sockets and remove test resources.

Command card

Prepare protected A.header and B.header files as described in the authorization module. The example assumes the local schema exposes document(id:). Replace it with the already observed query, not an invented production operation. The second request changes only identity.

mkdir -p evidence/protocols
cat > document-query.json <<'JSON'
{"query":"query($id:ID!){document(id:$id){id title}}","variables":{"id":"O1"}}
JSON
curl -q --noproxy '*' --max-time 10 --header @A.header \
  -H 'Content-Type: application/json' --data @document-query.json \
  -o evidence/protocols/a.json http://127.0.0.1:8000/graphql
curl -q --noproxy '*' --max-time 10 --header @B.header \
  -H 'Content-Type: application/json' --data @document-query.json \
  -o evidence/protocols/b.json http://127.0.0.1:8000/graphql

For a local socket, use the browser's Network → WS view to copy the observed message schema. Change one controlled room ID and send one message; protocol libraries often add framing, acknowledgements, or subscription IDs that a generic text frame would omit.

Report threshold and checklist

Report forbidden controlled data/action, a protected-field leak, or unauthorized ongoing delivery after the applicable revocation point. Reflection, introspection, websocket connectivity, and partial errors without forbidden output are discovery observations.

  • Normal client operation and schema preserved.
  • Same permission rule compared across available transports.
  • Application-level errors and partial output inspected.
  • Authentication at connection and message levels distinguished.
  • Every stream closed and fixture cleaned up.
Proxy normalization, cache isolation, and request boundariesInvestigate layered HTTP behavior with isolated cache keys and controlled front-end/back-end traffic.

Open canonical module and reverse references

Investigation sequence
  1. 01Map HTTP processing layers
  2. 02Prove isolation
  3. 03Establish normal cache behavior
  4. 04Change one interpretation input
  5. 05Verify second controlled client
  6. 06Purge and document

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and invariant

Use a local proxy plus application, or a program-provided isolated cache namespace and upstream pool. Record protocol on each hop, request normalization, cache key, cache policy, authentication placement, and whether connections are reused. The invariant is: all layers agree on the request target, request boundary, and which client may receive a cached representation. These tests can affect requests other than your own; an unproven random query parameter is not isolation.

Numbered test procedure

  1. Map the layers. From configuration or operator-provided logs, identify browser → CDN/proxy → application. Capture an ordinary controlled URL from two independent clients. Record Age, cache-status headers where present, Vary, Cache-Control, effective host/path, and origin logs. A header name or latency difference alone does not establish caching.
  2. Prove the test namespace. Configure a dedicated local path or verified cache-key component. Request two distinct canary paths and confirm they remain separate through origin and cache logs. A query string may be ignored, normalized, or partially keyed. If separation cannot be established in a shared environment, keep mutation experiments in the replica.
  3. Test cache poisoning. On a fresh isolated key, vary one input that changes origin output but may not be in the cache key, such as an observed forwarding header used in a generated absolute link. Use a harmless marker host under your control. Make a second request without that input and inspect whether the marker persists. Confirm a control key remains clean. The evidence must show an altered representation served from shared cache to the second controlled client. Cache poisoning.
  4. Test cache deception. Create a private page containing A's unique synthetic marker. In the lab, compare the canonical path with one suffix/path-normalization variant that the origin resolves to the same page but the cache may treat as static. Request that variant as A, then as anonymous B. Verify B receives A's marker because of caching; compare B's direct origin response or a fresh key. Sharing via an intentionally public capability URL is not deception. Cache deception.
  5. Test parsing agreement in isolation. Capture the bytes seen by front end and backend for a normal request. In the lab, create one malformed framing case and observe whether both components reject it consistently. For ambiguous length/framing, verify how many requests each layer parsed and where the second controlled request begins. Do not treat a timeout as proof, or use unrelated users' responses to demonstrate desynchronization. HTTP/1.1 framing and HTTP/2-to-HTTP/1 translation must be assessed separately. HTTP/1.1 framing, Request smuggling.
  6. Verify consequence and clean up. Save origin/cache logs, exact requests, normalized targets, cache keys, and client identities. Purge only the dedicated test namespace or restart the disposable cache. Report whether the outcome is poisoned content, private response disclosure, authorization normalization bypass, or request-boundary disagreement; those are distinct findings.

Command card

These ordinary requests establish a local cache baseline. Configure /bb-cache/case-01 and /bb-cache/case-02 as separate keys in your own proxy first. A later poisoning probe must vary only a specific source-identified input.

mkdir -p evidence/cache
curl -q --noproxy '*' --max-time 10 -D evidence/cache/first.h \
  -o evidence/cache/first.txt http://127.0.0.1:8080/bb-cache/case-01
curl -q --noproxy '*' --max-time 10 -D evidence/cache/second.h \
  -o evidence/cache/second.txt http://127.0.0.1:8080/bb-cache/case-01
curl -q --noproxy '*' --max-time 10 -D evidence/cache/control.h \
  -o evidence/cache/control.txt http://127.0.0.1:8080/bb-cache/case-02

Report threshold and checklist

Report a controlled second-client effect or demonstrated parser disagreement with a meaningful security boundary. Mere caching, unexpected 400/502 responses, reflected Host values, and differences caused by the testing proxy are common false positives.

  • Cache namespace and upstream isolation verified.
  • Every HTTP hop and protocol recorded.
  • Normal, mutated, second-client, and clean-key controls saved.
  • No production response-queue capture or broad desync probing.
  • Test cache entries purged after evidence collection.

Local curl examples bypass environment proxies with --noproxy '*'; -q alone only suppresses curl configuration files. These snippets target loopback and do not follow redirects. curl manual.

Cache semantics and revocation control

Interpret response directives precisely: unqualified no-cache permits storage but requires validation before reuse; no-store prohibits storage/reuse subject to the specification's defined exceptions; unqualified private prevents shared-cache storage but permits private-cache storage. Set-Cookie alone does not disable caching. Requests containing Authorization have special shared-cache restrictions, with explicit exceptions such as public and s-maxage; cookie-authenticated requests need their own correct response policy and cache partitioning. Read the effective configuration and observe behavior. RFC 9111, sections 3.5, 5.2 and 7.3.

In an isolated namespace, compare A, anonymous B, and a fresh control key after A changes or deletes the private marker. Also exercise one normal conditional request if the application uses validators. Inspect which identity the origin checked and which stored body the cache reused after a 304. Do not infer privacy from an Age: 0, absent cache header, or an initial miss. Report stale post-revocation access against the product's promised lifecycle, distinguishing a previously downloaded copy from a newly served shared response.

  • Storage, revalidation and private/shared-cache behavior distinguished.
  • Fresh, cached and post-change responses compared where applicable.
CI and releases: trace untrusted input into privileged workAudit workflow events, shell interpolation, untrusted builds, artifact consumers, and release identity as one privilege chain.

Open canonical module and reverse references

Investigation sequence
  1. 01Identify event trust
  2. 02Map executable input
  3. 03Inspect privileges
  4. 04Follow artifacts
  5. 05Validate locally
  6. 06Verify release identity

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Draw the actual privilege chain#

For every release-capable workflow, record event actor, workflow-file revision, checked-out revision, executable inputs, token permissions, reachable secrets, runner persistence, and final consumer. Distinguish data review from code execution: a checked-out contribution becomes executable through Makefiles, package install hooks, test discovery, plugins, and local actions as well as explicit shell scripts.

Use one row per job. Include reusable workflows and composite actions; apparent isolation at the top-level YAML may disappear inside their implementation. A permissive token setting without a lower-trust entry is a hardening observation, not a completed takeover claim.

Boundary Local evidence to collect Comparison control
Metadata → generated shell Original expression and resulting script Environment variable passed as data
Fork content → privileged build Exact fetched revision and executed file Trusted base revision only
Test artifact → publisher Producer identity, run association, consumed path Artifact from expected trusted build
Build → attestation → release Artifact digest and accepted signer identity Wrong-source artifact rejected

Triage commands and a safe coding pattern#

These examples inspect local files; they have not been run against a target. Grep-style matching deliberately overselects candidates.

rg -n --hidden -g '*.yml' -g '*.yaml' \
  'pull_request_target|workflow_run|issue_comment|permissions:|id-token:' \
  ./repo/.github
rg -n --hidden -g '*.yml' -g '*.yaml' \
  'download-artifact|upload-artifact|cache-mode|head\.sha|npm install|make ' \
  ./repo/.github

Passing untrusted metadata through an environment variable avoids inserting its contents into the generated shell program. Quote it and do not subsequently eval it. This pattern does not solve dangerous argument semantics in downstream tools. GitHub secure use.

- name: Inspect title as data
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: printf '%s\n' "$PR_TITLE"

Check current protections before declaring a pwn request#

GitHub currently documents fork-ref checkout protection for pull_request_target and read-only default cache access for that event. Inspect action revision, explicit allow-unsafe-pr-checkout, cache-mode, manual fetches, repository policy, and actual execution. The trigger name alone proves neither execution nor writable cache. Controls can differ from older published examples. Current pull_request_target guidance.

Hypothetical trace: an unprivileged test job uploads report.zip; a privileged follow-up selects an artifact by a generic name; an extracted helper is executed before publication. Validate run/repository binding, extraction destination, and consumer behavior in your own fixture with an inert marker and dummy credentials. A successful workflow conclusion does not authenticate every byte it produced. An artifact parsed strictly as data may break the supposed execution chain. GitHub Security Lab workflow analysis.

Follow identity to the shipped artifact#

For a known public artifact, this read-only verification example contacts GitHub. Replace repository, workflow, and commit with expected identities established independently; do not copy identities out of the untrusted artifact and then “verify” against them.

gh attestation verify ./dist/product.tar.gz \
  --repo OWNER/REPO \
  --signer-workflow OWNER/REPO/.github/workflows/release.yml \
  --source-digest EXPECTED_COMMIT \
  --format json > ./evidence/attestation.json

A reusable workflow may be the signer rather than the calling workflow. Verified provenance supports an origin claim; it does not prove the builder was uncompromised or the software is vulnerability-free. GitHub CLI verification.

Trusted publishing reduces long-lived publish-token exposure, but a compromised authorized workflow can still obtain publishing authority. Review environment approval, workflow identity, build separation, and remaining legacy credentials. PyPI trusted publisher security model.

Report and retest gate#

  • A permitted low-trust actor controls the demonstrated input.
  • Exact workflow/action revisions and security defaults are captured.
  • Inert local reproduction reaches the privileged consumer.
  • Real permissions are supported by configuration, not guessed from a trigger.
  • Artifact run binding and signer/source identity are checked.
  • Patched data handling rejects the case while normal releases still work.

Report the reachable capability and its prerequisites. Do not run target publishing steps or use discovered tokens to prove a local workflow hypothesis; maintainers can confirm live permissions through a coordinated reproduction when needed.

For GitLab repositories, also use GitLab pipeline boundaries: parent-project merge-request execution, job-token permissions and runner isolation have distinct semantics.

Dependencies: from advisory match to reachable impactSeparate composition, advisory matching, runtime reachability, and exploit prerequisites so dependency reports describe product impact.

Open canonical module and reverse references

Investigation sequence
  1. 01Inventory layers
  2. 02Resolve advisory
  3. 03Trace consumer
  4. 04Check prerequisites
  5. 05Compare fixed behavior
  6. 06Bound affected versions

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Keep four questions separate#

An SBOM answers what a tool identified in an artifact. An advisory matcher asks whether a component version matches known vulnerability data. Reachability asks whether the relevant implementation is used. Exploitability asks whether an attacker can satisfy the conditions and cross a security boundary. Preserve each conclusion independently; a negative result at one layer does not answer every other layer.

Inventory source locks, installed runtime, development/build dependencies, and final release output separately. A vulnerable parser in a lockfile may be absent from the production image. A development-only package may still run inside a privileged publisher. Record ecosystem, package identity, resolved version, origin, dependency path, optional features, and artifact digest. Use CI and release analysis for build impact and source data flow for runtime impact.

Inventory and match locally#

Examples require installed tools, reviewed inherited configuration, existing local paths, and prepared OSV ecosystem databases. They are command templates, not claimed results. ./release-root means an already extracted, reviewed release tree.

mkdir -p ./evidence
syft dir:./repo -o cyclonedx-json=./evidence/source.cdx.json
syft dir:./release-root -o cyclonedx-json=./evidence/release.cdx.json
osv-scanner scan source --offline -r --format json \
  ./repo > ./evidence/osv-source.json

Syft supports directory inventory and structured SBOM output; detected composition depends on catalogers and available metadata. Compare the two inventories rather than assuming they represent the same execution environment. Syft documentation.

OSV source scanning uses supported manifests and lockfiles. Full --offline requires cached databases and avoids network activity; --offline-vulnerabilities has a narrower meaning and can permit other network features. Missing caches are an incomplete scan. Preserve database age and scanner stderr alongside findings. OSV source scanning, offline behavior.

Build a reachability worksheet for each serious candidate#

Question Concrete evidence If unresolved
Is the resolved component affected? Exact advisory range, vendored source, backport patch Version match only
Is affected code present? Release tree, build flags, linked module Source-only candidate
Is the vulnerable API called? Consumer call site and dynamic trace Reachability unknown
Can an attacker control its argument? Ordinary route/file/CLI input and transformations No attacker path established
Are prerequisites satisfied? Parser mode, operating system, feature, privilege Conditional exposure
What changes across the boundary? Synthetic output or bounded local fault Impact unproven

Hypothetical example: a document service ships an affected archive library, but the advisory concerns symbolic-link extraction. Trace upload acceptance → import job → actual extraction API → filter configuration → destination. A safe filter can defeat that specific path even when the version matcher is correct. Conversely, an optional compatibility flag may restore the unsafe behavior. Test supported configurations explicitly and reuse the pinned lab.

Check package origin as well as version#

For dependency confusion, first inspect registry selection, namespace ownership, fallback, lockfiles, integrity hashes, and install hooks. pip documents that candidates from configured locations are considered together, rather than treating an extra index as a guaranteed lower-priority fallback. pip install index behavior.

Reproduce with two local registries and synthetic names that belong to your lab. Use packages containing metadata and harmless marker files; record which origin wins under the target's resolver configuration. Do not publish a real organization's missing package name. Resolver selection is one proof step; a product-impact claim must also show an affected build/consumer and applicable package controls.

Control, report, and completion#

Run the same smallest input against the affected and patched dependency in equivalent consumers. If swapping versions also changes application behavior or configuration, isolate that difference before crediting the patch. Test normal input to ensure the “fix” is not simply a broken integration. Avoid claiming every historical release is affected from one sampled version.

  • Source, runtime, build, and release inventories are labeled.
  • Advisory and patch are checked for the exact resolved component.
  • Backports, vendoring, and optional features are considered.
  • Attacker input reaches the affected API through supported use.
  • Local impact and a fixed/control case are preserved.
  • Tested versions, inferred exposure, and unknowns are separated.

The reporting threshold is a reproducible vulnerable use or the program's explicitly accepted dependency criterion. An old version number alone should remain a triage lead.

Cloud IAM: trust, delegation and effective permissionsProve which principal can act on which resource, including cross-account trust, delegated roles and workload federation.

Open canonical module and reverse references

Investigation sequence
  1. 01Pin account and principals
  2. 02Draw authority graph
  3. 03Establish allowed and denied controls
  4. 04Test one delegation edge
  5. 05Reproduce and remove fixtures

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and security invariant#

Use two owned accounts or explicitly supplied test identities: an administrator for fixtures, principal A with restricted rights, and principal B representing the allowed integration. Record account IDs, role ARNs, session type, region, policy versions and a synthetic object. The invariant is precise: A cannot obtain B's authority or make a service exercise it on A's behalf. Reuse the authorization matrix for rows and evidence naming.

Before cloud testing, check the current provider policy for the exact service and activity. AWS customer testing permission does not extend to attacking the provider itself. AWS testing policy

Procedure#

  1. Confirm identity, then map enforcement. Write one row for caller → trust policy → assumed role → service API → resource. Collect owner-supplied identity and resource policies, session restrictions, boundaries and organization restrictions. An isolated wildcard allow does not establish effective access. Preserve missing policy visibility as an explicit uncertainty. AWS policy evaluation

  2. Create paired controls. B performs one approved metadata lookup against the canary; A attempts the identical operation and should be denied. Use fresh sessions and the same resource identifier. If both fail, repair the fixture or connectivity before testing a vulnerability hypothesis.

  3. Change one trust input. With two owned integration identities, compare the documented allowed principal with the disallowed one. For a third-party integration, inspect who assigns the external ID and whether the service ties it to the correct customer. A missing external ID alone is not universally a vulnerability: demonstrate the applicable confused-deputy path. Third-party role access

  4. Test delegation separately. Review whether A can select a more powerful service role while creating or updating a workload. In an isolated account, use a single approved disposable job that only checks canary metadata. Compare its normal role with the proposed excessive role. Record both workload-creation permission and role trust; PassRole alone is insufficient. The relevant audit event is the service create/update operation because PassRole is a permission, not an independent API event. PassRole documentation

  5. Check federation claims. In an owned identity-provider fixture, change only one legitimate token claim: subject, audience, repository, branch or environment, as applicable. Do not forge another organization's identity. Record which rule rejects the negative case. Token correctness and cloud role selection are separate checks; reuse identity and sessions. OIDC role trust

Command card#

Replace identifiers with the fixture values. Run individually; the profiles must already use owned credentials. Commands inspect identity, trust and one object's metadata.

aws --profile lab-low sts get-caller-identity \
  --no-cli-pager --cli-connect-timeout 5 --cli-read-timeout 10

aws --profile lab-owner iam get-role --role-name BountyFixture \
  --query 'Role.{Arn:Arn,Trust:AssumeRolePolicyDocument}' \
  --no-cli-pager --cli-connect-timeout 5 --cli-read-timeout 10

aws --profile lab-low s3api head-object \
  --bucket OWNED_BUCKET --key bounty/canary.txt \
  --expected-bucket-owner YOUR_ACCOUNT_ID \
  --no-cli-pager --cli-connect-timeout 5 --cli-read-timeout 10

Repeat the last command with the allowed fixture profile. Expect metadata for the allowed principal and denial for A. A denied metadata request can also mean a missing object: establish existence using the owner control. GetRole CLI, HeadObject semantics, caller identity

Evidence, false positives and cleanup#

Save the principal/resource matrix, exact changed input, redacted policies, request IDs and one audit event. Distinguish policy candidate, successful authority acquisition, and demonstrated canary access. Intended administrator delegation, denied actions and policy simulation alone do not prove escalation. Delete disposable jobs and roles by recorded fixture IDs; revoke fixture sessions where supported and verify canary access is denied again.

  • Effective caller and resource owner captured.
  • Allowed and denied controls use the same operation.
  • Trust, service creation and data permission distinguished.
  • Tokens and live credentials excluded from the report.
  • Fixture removal and post-cleanup denial recorded.

The worked commands below use AWS. For Azure managed identities, management/data separation, and GCP service-account attachment versus impersonation, continue with Azure and GCP boundaries.

Object storage, signed URLs and delivery boundariesFollow one private canary through upload, signing, CDN delivery and expiry without enumerating real objects.

Open canonical module and reverse references

Investigation sequence
  1. 01Create private canaries
  2. 02Map origin and delivery
  3. 03Compare anonymous and tenant access
  4. 04Test capability scope
  5. 05Verify cleanup and expiry

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and invariant#

Create two tiny text objects in controlled storage, one private object per tenant, containing different random markers. Record bucket/account, exact keys, region, encryption requirements, application ownership and delivery host. Obtain the intended read, write, share and revocation rules. Only an entitled caller may mint or exercise the capability promised by the product. Reuse authorization testing for tenant changes; this module adds cloud storage and delivery behavior.

Procedure#

  1. Separate the access paths. Draw upload API → signer → storage object → CDN → browser. Add direct origin access, access points and background exports if the product uses them. Decide whether a file is meant to be public before calling anonymous retrieval a finding.

  2. Establish object-specific baselines. The owner must retrieve the exact marker; an unauthenticated client and the other tenant should fail where privacy is promised. Inspect owner-provided policy and effective public-access controls. A permissive bucket statement can be overridden by account or other public-access blocks; a block-status response does not examine every application sharing path. S3 public-access controls

  3. Inspect signed capability issuance. Ask tenant A to create its normal download or upload capability. Then change only the owned fixture's target key to B's key through the application's supported request. Compare authorization before signing and before asynchronous result release. Properly issued presigned URLs are intentionally usable by their holders; that property alone is not an authorization bug. S3 presigned URLs

  4. Test the promised restrictions. Compare the allowed method/object with a changed method or the other synthetic key. Check the agreed expiry using timestamps and a single later request, allowing for documented credential lifetime and clock behavior. For write tests, allocate a separate disposable key and explicit permission; never overwrite an existing customer file. Capture the owner-confirmed object version and marker before and after.

  5. Compare delivery and origin. Request the private fixture through the intended authenticated CDN path, then directly from its owned origin without credentials. The direct path should enforce the documented protection. A publicly intended origin is a valid control, not a bypass. CloudFront origin access control applies differently to normal S3 origins and website endpoints; match the actual configuration. CloudFront origin restrictions

Command card#

The first command reads at most 64 bytes of the exact fixture through unsigned S3 access. It performs no listing. Credentials, URLs and output files should stay private.

aws s3api get-object --bucket OWNED_BUCKET \
  --key bounty/private-canary.txt --range bytes=0-63 \
  --expected-bucket-owner YOUR_ACCOUNT_ID --no-sign-request \
  --no-cli-pager --cli-connect-timeout 5 --cli-read-timeout 10 \
  ./anonymous-canary.bin

aws --profile lab-owner s3 presign \
  s3://OWNED_BUCKET/bounty/private-canary.txt \
  --expires-in 60 --region YOUR_REGION \
  --cli-connect-timeout 5 --cli-read-timeout 10

curl -q --silent --show-error --max-time 10 --max-filesize 1024 \
  --dump-header ./delivery.headers \
  --output ./delivery-canary.txt \
  https://OWNED_DELIVERY_HOST/bounty/private-canary.txt

Use the second command only to establish an intentionally issued positive control. Do not paste its bearer URL into reports or shared shell history. The curl command follows no redirects: inspect any redirect destination before testing it. Expected evidence is the marker or an access denial, not merely HTTP 200. S3 GetObject CLI, presign CLI

Interpretation and completion#

Report the precise failed boundary: anonymous private read, unauthorized capability issuance, cross-tenant overwrite, or direct-origin bypass. A 403 cannot distinguish every encryption, ownership and permission cause; an HTML login page is not object exposure. Avoid speculative findings based only on predictable keys or a reusable valid share URL. For URL-driven storage imports, reuse SSRF boundaries.

  • Object owner, privacy promise and marker recorded.
  • Anonymous, entitled and other-tenant cases compared.
  • Signature lifetime distinguished from application-session lifetime.
  • Origin and delivery tested against the same fixture.
  • Fixture versions removed; temporary shares revoked where supported.
  • Cached copies handled according to the owner's cleanup procedure.

For Azure SAS types and Entra versus Shared Key access, or GCP object ACLs and uniform bucket-level access, use Azure and GCP boundaries alongside this shared canary procedure.

Container and Kubernetes isolationTest RBAC, workload identity, admission, networking and host exposure as separate enforcement layers.

Open canonical module and reverse references

Investigation sequence
  1. 01Pin lab and isolation promise
  2. 02Map permission layers
  3. 03Test canary access
  4. 04Compare admission and network controls
  5. 05Remove only recorded fixtures

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and invariant#

Use an isolated cluster or an explicitly provided test namespace with two tenant identities. Record context, API version, namespace, CNI, admission configuration, image digest and service accounts. Prepare one canary Secret and one inert HTTP service using administrator fixtures. A tenant may not acquire another tenant's data, workload identity or host authority through the deployment features it is allowed to use. A namespace name alone does not specify the isolation promise.

Procedure#

  1. Start from the actual low-privilege identity. Use its own kubeconfig context. Separate resource reads, workload creation, role binding, impersonation and token issuance. Review permission grants before using them. kubectl auth can-i asks the authorization layer; it does not prove admission acceptance or exploitation. RBAC risks, permission query reference

  2. Compare direct and indirect canary access. The fixture owner reads its canary; the restricted principal should be denied. If workload creation is permitted, inspect whether a pod can choose a more powerful service account or mount a forbidden namespace resource. In the lab, use a one-shot workload that checks only the synthetic marker and exits. Record its resulting service account and generated pod, not only the submitted template. Service-account identity and its authorization are separate configuration elements. Service accounts

  3. Exercise admission coverage. Use an allowed manifest as a positive control. Compare one owner-approved manifest that violates the advertised policy, then the equivalent supported update path. In a disposable cluster, compare admission responses and final persisted objects. Avoid actual privileged workloads when admission rejection already answers the test. Server dry-run still contacts admission components; it is not offline inspection. Admission webhook guidance

  4. Measure network isolation independently. From two pre-created test pods, request the same tiny canary endpoint once. Record source/destination labels, namespaces, port and policy direction. The allowed source should receive the marker; the disallowed source should not. A timeout is inconclusive until the service and allowed route work. Policy objects require an enforcing network implementation; do not infer isolation from YAML alone. NetworkPolicy behavior

  5. Inspect the container-to-host boundary. Record privileged mode, capabilities, host namespaces, host paths, socket mounts and UID mapping. Root inside a container does not establish host root. Where the owner supplies an isolated host fixture, a read of a synthetic host-mounted marker can demonstrate unintended filesystem exposure without reading host credentials. Compare with the restricted deployment. Pod Security Standards, Docker security

Command card#

These commands query one named resource or inspect one owned container. They do not create workloads.

kubectl --context lab-tenant-a --request-timeout=10s \
  -n tenant-b auth can-i get secrets/bounty-canary

kubectl --context lab-tenant-a --request-timeout=10s \
  -n tenant-a auth can-i create pods

kubectl --context lab-owner --request-timeout=10s \
  -n tenant-a get pod bounty-probe \
  -o jsonpath='{.spec.serviceAccountName}{"\n"}'

docker --context lab-engine container inspect bounty-probe \
  --format '{{json .HostConfig.Privileged}} {{json .HostConfig.Binds}}'

Expect no for cross-tenant Secret access. A yes is an authorization finding candidate; verify only the supplied canary when reading is allowed. The inspect output establishes configuration, not an escape exploit. Docker inspect

Evidence, cleanup and checklist#

Store the starting role, requested object, admission decision, resulting workload, canary result and comparison case. Link business-level ownership to the shared authorization matrix. False positives include intended cluster administrators, intentionally shared namespaces and policies ineffective only because the lab omitted the production CNI. Describe those limitations explicitly.

  • Namespace, workload, node and network boundaries separated.
  • Low-privilege context used for reproduction.
  • Only synthetic Secrets and markers accessed.
  • Admission acceptance distinguished from runtime impact.
  • Saved fixture UIDs checked before deletion.
  • Disposable pods, role bindings and markers removed without deleting shared namespaces.
Infrastructure configuration: from repository default to deployed impactTrace IaC, charts, Compose and image packaging into supported deployments, including exposed services and sensitive artifacts.

Open canonical module and reverse references

Investigation sequence
  1. 01Freeze supported configuration
  2. 02Render with synthetic values
  3. 03Trace risky settings
  4. 04Compare deployed controls
  5. 05Remove artifacts and resources

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and invariant#

Choose one supported deployment path: Terraform workspace, Helm chart, Docker Compose stack or image build. Pin the repository commit, lockfiles, provider/chart versions, image digest and documented values. Use an isolated environment with synthetic credentials and no workstation cloud credentials. A supported configuration must preserve its documented trust boundaries and keep deployment secrets away from unintended readers. A linter warning is a hypothesis until the affected deployment and boundary are established.

Procedure#

  1. Reconstruct the real defaults. Read quick-start instructions, production overrides, environment-variable precedence and optional features. Record which configuration a normal user actually follows. Compare the default deployment with the maintainer-recommended secure profile. A development-only example is not automatically a production vulnerability.

  2. Render trusted configuration. Render supplied charts or Compose files with synthetic values, then inspect resulting listeners, host publications, identities, volumes and access policies. Keep rendered files private: interpolation can materialize credentials. Rendering is useful evidence of intended resources, but it does not prove deployment reachability. Helm template, Compose configuration

  3. Trace secret lifetime. Follow a synthetic marker from input variable through plan, state, output, image layer, artifact archive and logs. Ask which principal can read each artifact. Terraform's sensitive designation mainly controls display; state or JSON output can still contain values. Prefer a known fixture plan over reading live state. Do not run unreviewed initialization, provider binaries or provisioning hooks on a credentialed workstation. Terraform sensitive data, Terraform show

  4. Inspect packaging separately from runtime. For an owned image containing synthetic inputs, review build history and layers against the build recipe. A secret removed from a later filesystem layer may still exist in earlier content. Check whether build arguments or environment variables carry credentials where a build-secret mechanism is appropriate. Record the exact image digest and artifact accessibility; a random string match does not establish a live credential. Docker build secrets

  5. Confirm one deployed consequence. In the disposable deployment, test one known canary from the intended allowed location and an owner-controlled disallowed location. For exposed databases, reuse network-service boundaries. For broad roles or storage policies, reuse cloud IAM and storage delivery. Change only the relevant setting, redeploy the lab fixture and repeat the pair.

Command card#

Use a reviewed, already initialized Terraform fixture and trusted chart. These examples write only local evidence; commands are not instructions to initialize an arbitrary repository.

umask 077

helm template bounty ./trusted-chart \
  --values ./fixture-values.yaml > ./fixture-rendered.yaml

terraform -chdir=./lab-iac show -json ./fixture.tfplan \
  > ./fixture-plan.json

docker image history OWNED_SYNTHETIC_IMAGE \
  --no-trunc --format '{{.CreatedBy}}' \
  > ./fixture-history.txt

rg --files-with-matches --fixed-strings \
  'BB_SYNTHETIC_SECRET_2026' \
  ./fixture-plan.json ./fixture-history.txt ./fixture-rendered.yaml

A returned filename proves only that the chosen marker is present in that file. The security result also needs unintended artifact access and the intended confidentiality rule. Restrict provider execution to the disposable lab even for inspection commands that may need installed plugins. Image history reference

Interpretation, cleanup and checklist#

The report should connect supported input → rendered setting → deployed resource → unintended reader or actor → canary result. Include the secure comparison and minimum affected configuration. Avoid claiming that every deployment is exposed when a particular override is required. Public ports without usable privilege, fake example secrets, intentional debug builds and unreachable rules are common false positives.

  • Supported mode and exact dependency versions pinned.
  • Default values separated from operator overrides.
  • Synthetic data used throughout artifacts.
  • Actual artifact reader or service boundary identified.
  • Remediated comparison reproduces the expected denial.
  • Only fixture resource IDs destroyed; volumes, plans, logs and disposable credentials cleaned up.
DNS, TLS and email trust boundariesSeparate dangling records, transfer/recursion exposure, TLS identity and SMTP relay into controlled protocol tests.

Open canonical module and reverse references

Investigation sequence
  1. 01Pin owned services and trust promise
  2. 02Collect exact protocol baseline
  3. 03Run one paired control
  4. 04Correlate protocol and receiver evidence
  5. 05Remove synthetic mail and fixtures

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and invariant#

Treat DNS, TLS and SMTP as separate services with explicit owners, addresses, ports and test methods. Use an owned miniature zone, known certificate hostname and researcher-controlled mail sender and receiver. A service must enforce the claimed ownership, peer identity or relay boundary. Define the expected result before probing: public DNS answers are normally intentional; private zone transfer or unauthenticated external relay may not be.

Procedure#

  1. Inspect DNS ownership without claiming resources. Record the exact CNAME or delegation chain, provider error, TTL and owner-supplied resource inventory. A missing backing resource creates a takeover candidate; it does not prove that another account can bind the hostname. Check provider validation and reservation rules. Do not claim dangling resources as a generic proof step. Azure dangling DNS guidance

  2. Separate transfer and recursion. AXFR requests the zone contents, so test only an owned small zone or an explicitly supplied fixture. Compare the permitted transfer source with the denied source. For recursion, query one unique name in an owned authoritative zone through the tested resolver; correlate the authoritative log with the resolver response. Cached answers and the RA flag alone do not prove unrestricted recursion. AXFR protocol, BIND configuration

  3. Verify TLS identity as the real client. Use the intended hostname, SNI, port and trust store. Compare the valid fixture certificate with an owned wrong-host certificate in a local client integration test. OpenSSL diagnostics confirm the fixture but do not establish that the product's client accepts an invalid identity. Certificate-chain trust and hostname matching both matter. OpenSSL s_client

  4. Test SMTP relay with one synthetic message. Only when message delivery is allowed, use sender and receiver domains controlled by the researcher and outside the tested relay's local destination domains. Start from an untrusted source address with no SMTP authentication. The secure control rejects external-to-external relay; the permitted control uses the documented authenticated submission path. A test from an allowlisted office or VPN address does not prove an open relay.

  5. Correlate SMTP acceptance with delivery. Retain the envelope, transcript, unique subject marker and receiver logs. A successful RCPT response or queue ID alone does not establish final delivery. Keep relay testing separate from SPF, DKIM and DMARC alignment findings. For sender-identity tests, the receiving policy and trusted authentication results determine the demonstrated impact. SMTP protocol

Command card#

Replace all example addresses only with owned fixtures. Run one command at a time. The AXFR example is limited to the small fixture zone; it retrieves that zone.

timeout 10 dig -r @192.0.2.53 fixture.example AXFR \
  +tcp +time=2 +tries=1

timeout 15 openssl s_client -connect tls.fixture.example:443 \
  -servername tls.fixture.example \
  -verify_hostname tls.fixture.example \
  -verify_return_error -brief </dev/null

timeout 20 swaks --config --server relay.fixture.example --port 25 \
  --timeout 10 --from probe@sender.fixture.example \
  --to receiver@recipient.fixture.example \
  --header 'Subject: Bounty relay fixture 2026-01' \
  --body 'Synthetic authorized relay test; no action required.'

Use a clean shell with no SWAKS_OPT_* overrides; --config disables automatic configuration files, not environment settings. Confirm the transcript contains no AUTH and exactly one intended recipient. The fixture message contains no credentials; this example does not claim transport-security testing. BIND dig, Swaks reference

Result and cleanup checklist#

  • Public records distinguished from restricted zone contents.
  • Dangling DNS labeled candidate unless claimability is established.
  • TLS finding reproduced in the product's actual client.
  • Relay test source is outside trusted networks.
  • Acceptance, queueing and delivered fixture correlated.
  • No real recipients contacted; synthetic mail and temporary zone/certificate fixtures removed.
Database, cache, queue and management service boundariesMove from a reachable socket to a minimal proof of unintended service authority using synthetic records and paired identities.

Open canonical module and reverse references

Investigation sequence
  1. 01Record exact endpoint and allowed method
  2. 02Establish authenticated baseline
  3. 03Read one forbidden fixture
  4. 04Compare identity and network controls
  5. 05Document and remove canaries

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and invariant#

Record one service row: owner, exact IPv4 or IPv6 address, hostname, transport, port, product build, authentication method and intended access boundary. A hostname in web scope does not automatically include every backing service. Prepare a restricted account, an allowed account and one synthetic key, row, queue or management fixture. A network position or low-privilege account must not gain the protected data or administrative operation outside its stated permissions.

Use version banners only to select hypotheses. Vendor backports and disabled features can invalidate a banner-to-CVE match. First establish a normal connection, intended authenticated operation and expected rejection. Keep source network and timestamps constant when comparing identities.

Procedure#

  1. Separate reachability from authority. Record a connection outcome without treating an open port as a vulnerability. Check whether the service is intentionally public, behind mutual TLS or protected by native authentication. A health response is not administrative access.

  2. Use one exact resource. For Redis, request only the agreed canary key; do not run keyspace enumeration, configuration changes or flush commands. Compare anonymous access, the permitted principal and the restricted principal. Redis network protection, authentication, ACLs and TLS solve different parts of this boundary. Redis security

  3. Check connection rules and database grants independently. In PostgreSQL, examine the owner-supplied host-based authentication rule selected by connection type, source, database and user. Establish whether authentication genuinely happened, then test permission for one fixture row. A trusted local administration connection is not evidence of internet exposure. PostgreSQL host authentication

  4. Model queues as stateful resources. In an isolated broker, create two fixture virtual hosts and named queues. Compare whether A can declare, publish to or consume B's fixture according to the contract. Use a single marked message and account for acknowledgement behavior; consuming production messages changes application state. Broker login does not imply permissions across all virtual hosts. RabbitMQ access control

  5. Evaluate management APIs and network parity. For an owned Docker daemon, review listener and client authentication configuration; use only an approved metadata endpoint for the initial proof. Do not create privileged containers to confirm broad authority on production. Compare an explicitly owned IPv4 and IPv6 service only when both are in scope: different exposure is relevant when it defeats the same promised restriction. Docker daemon protection

Command card#

The Redis endpoint below is an isolated local fixture. The environment removal prevents an inherited password from contaminating the unauthenticated control. The database example uses the supplied test account and private password file, with TLS identity verification and a read-only transaction.

env -u REDISCLI_AUTH timeout 10 redis-cli \
  -h 127.0.0.1 -p 16379 --raw GET bounty:canary

timeout 15 psql -X -w \
  "host=db.fixture.example port=5432 dbname=bounty_fixture user=lab_low sslmode=verify-full sslrootcert=./fixture-ca.pem connect_timeout=5 options='-c statement_timeout=3000'" \
  -v ON_ERROR_STOP=1 \
  -c "BEGIN READ ONLY; SELECT marker FROM bounty_canary WHERE id=1; ROLLBACK;"

An unauthenticated marker demonstrates read access to that fixture. NOAUTH, NOPERM, connection failure and an absent value mean different things; compare the known-good control. For PostgreSQL, distinguish TLS failure, authentication failure and SQL permission denial. Credentials can come from a password file even with -w; that flag disables prompting, not password authentication. Redis CLI, PostgreSQL connection parameters, psql options

Evidence, false positives and cleanup#

Save the endpoint, source, verified account, precise operation, response, fixture ownership and comparison. Public service banners, intentionally anonymous health checks and intended shared queues are not equivalent to data exposure. Reuse authorization matrices for per-resource checks and identity/session testing for revocation behavior.

  • Allowed and denied controls have working connectivity.
  • No wildcard reads, real queue consumption or configuration mutation.
  • Network access distinguished from authentication and authorization.
  • Lab result separated from unverified production applicability.
  • Canary records, messages and test accounts removed using their recorded identifiers.
MFA and passkeys: enrollment, proof, replacement, and recoveryFollow each authenticator from account binding to removal, and validate WebAuthn assertions without mistaking client UI or counter behavior for a security guarantee.

Open canonical module and reverse references

Investigation sequence
  1. 01Record assurance policy
  2. 02Enroll controlled factors
  3. 03Compare fresh and stale proof
  4. 04Check assertion bindings
  5. 05Replace and recover
  6. 06Verify removal

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and invariant

Reuse identity/session fixtures. Prepare two disposable accounts, recovery access, separate browser profiles, and one reversible protected operation. Record whether the product promises MFA, passwordless login, recent reauthentication, or a particular authenticator class; these are different contracts. The invariant is: a proof must authorize the intended account, ceremony, and assurance level, including factor management. Capture the server's stored factor list and audit events, not just a green browser badge.

Numbered procedure

  1. Test enrollment before authentication. Register A's first and second factors through the legitimate UI. Compare an authenticated registration with anonymous, MFA-pending, and stale-session attempts to complete that same observed operation. For an account-bound enrollment flow, begin in A and attempt completion in B, changing only the account/session context. Confirm the final factor owner with a fresh login. For username-less authentication, successful login to the factor's rightful owner is expected; it must not be reported as cross-account bypass.
  2. Exercise replacement and removal. Capture normal add, rename, delete, recovery-code regeneration, and fallback changes. In the replica, replay one sensitive factor change without the required recent proof, then with a fresh valid proof. Verify whether a newly added factor can log in and whether the removed factor still authenticates. Preserve a recovery route throughout. A factor addition that only changes a label proves nothing. Factor replacement deserves the same care as initial login. OWASP MFA management.
  3. Test OTP and recovery consumption separately. Use A's successful OTP once more in a fresh pending login during its validity window; use an invalid code as the negative control. Test one recovery code, then attempt the consumed code again. Regenerate the recovery-code set and check one old code. Do not brute-force codes or trigger real push/SMS floods. Record which transaction succeeded, not merely whether the numeric value appeared twice. TOTP verification must prevent reuse after successful validation; an identical code displayed during one time step is normal. RFC 6238. Saved recovery credentials also need explicit lifecycle controls. NIST authenticator management.
  4. Build a real passkey baseline. In the local application, open DevTools → More tools → WebAuthn, enable the virtual authenticator, select CTAP2 and the required resident-key/user-verification capabilities, then register through the application's normal page. Record the RP ID and credential owner. This tests server integration; it does not demonstrate compromise of a hardware authenticator. Chrome virtual-authenticator workflow.
  5. Separate signature checks from semantic checks. Compare a fresh assertion with a consumed assertion and one with an invalid signature. For username-first login, verify the credential belongs to the intended account; for discoverable credentials, verify the returned user handle resolves consistently to the credential owner. Trace the verifier's challenge, ceremony type, origin, RP-ID hash, signature, and presence/verification decisions. A local authenticator fixture can sign a deliberately mismatched case; changing signed JSON without re-signing only tests signature rejection. WebAuthn assertion fields.
  6. Check policy-sensitive WebAuthn branches. Verify UV when the ceremony requires user verification; a touch proving user presence does not by itself prove UV. Account for an explicitly configured related-origin deployment and applicable legacy AppID handling before calling an origin/RP mismatch a bypass. Counter anomalies are risk signals, not standalone clone proof; some authenticators keep zero counters. Synced credentials and absent attestation are not automatically weaknesses. Evaluate the deployed library and RP policy against the pinned WebAuthn Level 3 Recommendation.

Local inspection card

Export an owned test assertion in the application's JSON transport format to assertion.json. This prints selected metadata and a challenge fingerprint; it does not verify the signature or establish a vulnerability. If your wrapper uses different keys, adapt the field path.

python3 - <<'PYCODE'
import base64, hashlib, json
from pathlib import Path
x = json.loads(Path('assertion.json').read_text())
s = x['response']['clientDataJSON']
c = json.loads(base64.urlsafe_b64decode(s + '=' * (-len(s) % 4)))
print(json.dumps({
    'type': c.get('type'), 'origin': c.get('origin'),
    'crossOrigin': c.get('crossOrigin'),
    'challenge_sha256': hashlib.sha256(c['challenge'].encode()).hexdigest()
}, indent=2))
PYCODE

Evidence and completion

Report a newly usable unauthorized factor, authentication below a required assurance level, or acceptance of a proof for the wrong account/transaction. Attach the expected rule, before/after factors, request order, independent fresh-login result, and cleanup. Missing notification alone is hardening evidence; a successful server rejection is a passed check.

  • Enrollment and replacement evaluated independently.
  • OTP, backup codes, and passkey ceremonies have separate controls.
  • Username-first and discoverable flows distinguished.
  • Signature rejection separated from signed-context validation.
  • Removed test factors and credentials no longer authenticate.
  • Virtual authenticator exports and live recovery secrets excluded from reports.
OAuth, OIDC, and SAML: transaction binding and token trustSeparate browser transaction binding, issuer trust, token purpose, and identity linking using controlled federation fixtures.

Open canonical module and reverse references

Investigation sequence
  1. 01Map issuers and clients
  2. 02Capture authorized flow
  3. 03Alter one binding
  4. 04Verify token semantics
  5. 05Check replay and linking
  6. 06Remove test grants

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and trust record

Use two controlled accounts, two registered lab clients when available, and a test identity provider. Record authorization server, client ID/type, exact redirect URI, grant, scopes, resource/audience, browser transaction, issuer/subject, and resulting local account. Pin deployed protocol profiles and libraries. Reuse the session lifecycle and authorization matrix; this procedure adds federation-specific decisions.

The invariant is: a valid credential for one transaction, client, issuer, or purpose must not silently authorize another. A decoded token, valid cryptographic signature, and successful login are three different observations.

Numbered procedure

  1. Establish browser binding. Capture a normal authorization-code login and a separate account-link operation. In the replica, deliver A's response into B's pending transaction and compare the resulting local account/link with untouched controls. Track which session initiated each transaction. Missing state alone is not decisive when transaction-specific PKCE or another valid mechanism provides the required binding. Validate a registered redirect with exact matching, accounting for the native loopback-port exception. RFC 9700.
  2. Check code and PKCE handling independently. On separate fresh authorization codes, compare the correct verifier, a wrong verifier, and a missing verifier. Then replay one already redeemed code. Where the flow supports PKCE, inspect whether removing the challenge can create a downgrade path. Keep client authentication, client ID, and redirect constant so rejection can be attributed correctly. A verifier is secret; a public challenge is not. Generate a new verifier for each real transaction. PKCE specification.
  3. Validate OIDC context. With the lab issuer, produce otherwise valid fixtures with one wrong issuer, client audience, expired time, or mismatched nonce when that flow requires it. Check profile-specific azp, hash, and authentication-age rules rather than assuming every claim is universal. Local identity should use the issuer/subject pair; equal email claims do not establish that two identities may be merged. Compare UserInfo subject with the ID-token subject when UserInfo is used. OpenID Connect Core.
  4. Test JWT trust and purpose. First show rejection of one invalid signature. Then use validly signed local fixtures for another resource or purpose: an ID token at an access-token endpoint, or an email-verification token at a session endpoint, where the implementation exposes that path. In source review, inspect algorithm allowlists, issuer-selected key material, key-ID lookup, and mutually exclusive validation rules. Do not turn a token-supplied key URL into a production callback experiment. A signature-valid but unauthorized token is a semantic trust failure, not necessarily broken cryptography. RFC 8725.
  5. Check refresh replay under the actual design. Record original and rotated refresh credentials separately. After one normal rotation, attempt the previous credential once from another local client and inspect both the returned token and a harmless resource read. If tokens are sender-constrained, test the relevant missing/wrong sender proof instead. Public-client refresh tokens require rotation or sender constraint; documented retry grace affects interpretation but does not erase demonstrated independent unauthorized access. OAuth refresh-token protection.
  6. Follow SAML from verified bytes to identity. In a local IdP/SP pair, compare a normal response with independently changed signed content, a wrongly targeted assertion, an expired assertion, and one replay. Trace the exact signed element from which the SP consumes identity and attributes; verifying an unrelated signed element is insufficient. For SP-initiated SSO check request correlation. For supported IdP-initiated SSO, missing request correlation is expected, but replay and recipient/trust checks still matter. Keep RelayState handling separate from assertion integrity. OWASP SAML processing.

Local command card

Prepare expected.header and other-resource.header using valid tokens minted by your lab issuer; each contains its complete Authorization: Bearer … line. The second token is intentionally valid only for a different resource under the issuer policy. Preserve the same subject and request where possible. The protected route is illustrative. These requests do not test a public provider.

mkdir -p evidence/federation
chmod 600 expected.header other-resource.header
curl -q --noproxy '*' --max-time 10 --header @expected.header \
  -D evidence/federation/expected.h -o evidence/federation/expected.json \
  http://127.0.0.1:8000/api/me
curl -q --noproxy '*' --max-time 10 --header @other-resource.header \
  -D evidence/federation/wrong-resource.h -o evidence/federation/wrong-resource.json \
  http://127.0.0.1:8000/api/me

A 401/403 is a useful control, but also inspect bodies and resulting identity. Successful use of the expected token establishes only the baseline. Token transport and audience selection must match the actual local deployment.

Evidence and checklist

Capture one invalid condition, accepted identity or capability, minimal synthetic response, and the intended trust configuration. Document any test signing keys you supplied: possession of the real IdP signing key already grants authority and is not a new bypass. A rejected code, intentional multi-audience policy, or unconsumed altered claim is not takeover evidence.

  • Browser binding and token validity tested separately.
  • Correct, wrong, omitted, and consumed proof cases kept distinct.
  • Token purpose and issuer/audience validation separated from signature validation.
  • SAML consumed identity traced to the validated signed element.
  • Grants, sessions, test keys, and transient credentials removed after evidence capture.
Request values: duplicate parameters, parser conflicts and downstream queriesTrace the value a security layer validates through to the value the application or downstream service actually consumes.

Open canonical module and reverse references

Investigation sequence
  1. 01Map parser chain
  2. 02Freeze two object controls
  3. 03Vary one representation
  4. 04Trace authorized value
  5. 05Inspect consumed value
  6. 06Prove controlled impact

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and invariant

Map the actual processing chain: gateway, framework parser, schema validator, authorization code, service client, and worker. Use two synthetic objects, A-readable O1 and B-only O2, in a local replica with per-layer logs. The invariant is: the value authorized is the same value the protected operation consumes. This procedure covers application values; HTTP message framing stays in the proxy/cache procedure.

Numbered test procedure

  1. Create a value ledger. Capture raw request bytes, content type, parsed field type, validated value, authorized object and final downstream request. Start with O1 allowed and O2 denied under A's identity. Disable response caching for this local comparison. List which locations are accepted for each field: path, query, JSON, form, or multipart. Do not infer framework-wide behavior from a single handler.
  2. Compare duplicate parameters. Submit the original scalar, the alternative scalar, then the two values together in each order. Follow the value through validation and consumption. First-value, last-value, list or rejection behavior is not a vulnerability alone. A finding needs a security decision on O1 followed by an operation on O2, or another demonstrated rule violation. Test one observed parameter rather than fuzzing every field. WSTG parameter pollution.
  3. Preserve duplicate JSON names. Construct raw JSON bytes instead of a language dictionary, which may discard duplicates before sending. Compare a valid string, a repeated name, and a schema-invalid type such as a two-element array when a scalar is required. Record whether the gateway validates the first occurrence while the application consumes the last. RFC 8259 recommends unique object names and describes divergent receiver behavior; it does not make every accepted duplicate a vulnerability. Avoid asserting that all compliant parsers must reject them. RFC 8259, section 4.
  4. Check location and normalization precedence. Where the real endpoint accepts more than one representation, compare a query field against the same JSON/form field and a path ID against a body ID. In separate trials, inspect one relevant percent-decoding or Unicode-normalization difference. Only send supported content types; prove which route and middleware ran. A different error response or equivalent spelling alone is not an authorization bypass.
  5. Inspect server-created requests. A single external scalar may be interpolated into an internal query string or path. In a local service stub, use a marker such as BB01&bb_probe=BB02 as the complete outer value and inspect whether it remains one downstream value or becomes an added parameter. Encode it for the outer request exactly once. If a delimiter appears downstream, use only source-confirmed synthetic fields to establish a changed policy decision; do not guess internal administrative operations. Contrast correct URL-builder encoding with string concatenation. Server-side parameter pollution.
  6. Confirm the boundary. Replay the minimal case through the normal ingress path and compare the strict-schema/consistent-parser negative control. Save the values each layer used and A's resulting access to the controlled O2 marker. Separate a reachable production path from a test harness configured with deliberately contradictory parsers. Remove both fixtures.

Command card: duplicate-preserving local requests

Assume an observed local /api/object-preview endpoint accepts scalar object, with test identities in A.header. Configure O1 as A-readable and O2 as B-only. The route names are placeholders, not discovered endpoints. This makes four ordinary, sequential requests without redirect following or inherited proxies.

mkdir -p evidence/parameters
curl -q --noproxy '*' --max-time 10 --header @A.header \
  -o evidence/parameters/allowed.txt \
  'http://127.0.0.1:8000/api/object-preview?object=O1'
curl -q --noproxy '*' --max-time 10 --header @A.header \
  -o evidence/parameters/denied.txt \
  'http://127.0.0.1:8000/api/object-preview?object=O2'
curl -q --noproxy '*' --max-time 10 --header @A.header \
  -o evidence/parameters/forward.txt \
  'http://127.0.0.1:8000/api/object-preview?object=O1&object=O2'
curl -q --noproxy '*' --max-time 10 --header @A.header \
  -o evidence/parameters/reverse.txt \
  'http://127.0.0.1:8000/api/object-preview?object=O2&object=O1'

For a JSON variation, preserve the literal repeated-name body in a file and use --data-binary @file with the correct content type; do not reconstruct it using a dictionary or UI form. curl data handling.

Report threshold and checklist

The report must identify the disagreeing components, raw ambiguous input, protected value checked, different value consumed, and controlled impact. A gateway-only rejection, intentional multivalue API, simple parser preference, or a client that changed the payload is insufficient. Use CWE-436 for an established interpretation conflict; add the specific resulting access or injection weakness where supported.

  • Allowed and forbidden single-value controls recorded.
  • Raw duplicates survived the test client.
  • Parsed types, precedence and downstream construction recorded.
  • Exact authorization/consumption mismatch demonstrated.
  • Fixed-parser or fixed-encoding negative control compared.
Webhooks and integration events: authenticity, tenant binding and replayVerify the event signature, provider-to-tenant binding and durable worker effect together across retries and out-of-order delivery.

Open canonical module and reverse references

Investigation sequence
  1. 01Capture genuine event
  2. 02Verify signed bytes
  3. 03Bind provider and tenant
  4. 04Check replay and order
  5. 05Inspect durable effect
  6. 06Revoke and clean up

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Setup and invariant

Treat an integration event as a separate privileged entry point. Inventory the subscription, receiving route, signing/authentication scheme, provider account, installation, tenant mapping, accepted event types, queue and final consumer. Use a local replica, a provider sandbox or local provider stub, two controlled tenants, and a reversible nonfinancial action. The invariant is: only an authentic, correctly bound event may cause its permitted effect, once under the workflow's rules.

Keep inbound event authenticity separate from outbound destination checks and object authorization. A valid signature does not itself identify which local tenant may be changed.

Numbered test procedure

  1. Capture a genuine baseline. Generate one ordinary event through the controlled integration. Save exact body bytes, verification headers, event/object IDs, provider account and mode, local tenant, queue ID and final state. Write down the expected change, such as granting one sandbox entitlement. Capture a correctly signed irrelevant event as a no-effect control. An HTTP 2xx can acknowledge receipt before the worker runs.
  2. Test authenticity before side effects. In the replica, compare the baseline with a missing signature, an invalid signature, and a one-byte body change retaining the original signature. Restore the local object, queue and deduplication fixture between authenticity trials: an already-consumed event can conceal a missing verifier. Follow both receiver and worker logs; rejection after a privileged item was accepted is too late. Respect the actual verification scheme. GitHub's documented HMAC covers payload bytes, so JSON reserialization changes the signed bytes; separately supplied event/delivery headers are not automatically covered. Prefer the official verifier and confirm failures stop processing. GitHub verification.
  3. Bind provider identity to the local object. Use valid sandbox events from two controlled provider accounts/installations. Verify each maps through trusted configuration to its correct tenant and provider object. For a local mutation experiment, change one untrusted routing field or unsigned header; a signed-field mutation requires an appropriately signed stub event and is a source-test hypothesis until its real attacker path is established. Check test/live mode and disconnected integrations. Do not claim a production forgery merely because the tester controls the lab signing key.
  4. Separate freshness, duplicates and order. Replay one valid event once and inspect durable effects. If the provider signs a timestamp, check the documented acceptance window using a local clock/fixture. A fresh signature is not a deduplication guarantee. Deliver two distinct legitimate events in reverse order in the sandbox and verify that an old update cannot undo a later revocation. Stripe documents duplicate delivery and does not guarantee event order; event creation seconds are not a safe total ordering or deduplication key. Use event identity and the product's object-state rules. Stripe webhook delivery behavior.
  5. Check dispatch and lifecycle. Compare the allowed event type/action with a genuine unsupported type and malformed-but-authentic local fixture. Ensure a loosely selected handler does not treat every event as success. Rotate a sandbox signing secret, record the intentional overlap, then check the old secret after that overlap ends. Remove the integration and test the documented handling of already queued events. GitHub recommends checking event type/action and keeps the delivery ID unchanged on redelivery. Treat any deduplication based on an unsigned header according to the actual authenticated binding. GitHub best practices.
  6. Inspect the transaction boundary. In the replica only, introduce one controlled worker failure before or after the effect, then retry. Check whether deduplication and the protected update can disagree, causing lost processing or duplicate effects. Use the existing state/replay procedure for transactional controls. Restore fixtures, cancel queued test deliveries, and remove local capture files when evidence has been redacted.

Command card: signed-body tamper control

Prepare event-valid.json and event-valid.headers from a newly generated local/sandbox delivery. The headers file contains only required verification and event metadata, not copied Host, Content-Length or production cookies. Prepare event-tampered.json by changing one known marker byte without re-signing. Start each authenticity case from the same clean local snapshot, including queue and deduplication state. Use a nonfinancial event and remain inside any signature freshness window.

mkdir -p evidence/webhooks
curl -q --noproxy '*' --max-time 10 \
  --header @event-valid.headers -H 'Content-Type: application/json' \
  --data-binary @event-valid.json \
  -D evidence/webhooks/valid.h -o evidence/webhooks/valid.txt \
  http://127.0.0.1:8000/webhooks/lab
# Inspect the valid effect, then restore the local fixture before this case.
curl -q --noproxy '*' --max-time 10 \
  --header @event-valid.headers -H 'Content-Type: application/json' \
  --data-binary @event-tampered.json \
  -D evidence/webhooks/tampered.h -o evidence/webhooks/tampered.txt \
  http://127.0.0.1:8000/webhooks/lab

For the separate replay experiment, restore once, send the valid request, wait for completion, and repeat it once without resetting. Inspect the durable effect count; two acknowledgements can correctly refer to one effect. The endpoint is illustrative. --data-binary preserves file bytes; these requests do not follow redirects. curl manual.

Report threshold and checklist

Report a forged protected transition, authentic event applied to the wrong controlled tenant, or replay/order failure with a durable security consequence. Acceptance of an intentionally unsigned public notification, permitted secret-rotation overlap, a harmless no-op acknowledgement, or a mock-only signing capability is not enough.

  • Exact authenticated bytes and unsigned metadata distinguished.
  • Provider identity, tenant, object and mode binding verified.
  • Invalid/tampered controls caused no privileged worker effect.
  • Freshness, duplicate delivery and order tested separately.
  • Final state, retry behavior and cleanup recorded.
GitLab pipelines: contribution, job token and runner boundariesTrace GitLab execution context, included configuration, downstream inputs, job-token permissions and runner reuse using private synthetic fixtures.

Open canonical module and reverse references

Investigation sequence
  1. 01Pin version and pipeline context
  2. 02Resolve included configuration
  3. 03Map job and downstream authority
  4. 04Compare one private fixture
  5. 05Reset runner and artifacts

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Scope and fixture contract#

Use an owned GitLab project pair and disposable runner, or owner-supplied configuration exports. Pin GitLab and Runner versions, feature flags, repository commit, pipeline ID, source project, target project and triggering user. Create a private synthetic artifact in project B and one job in A. Control over A's contribution or job must not acquire B's private authority or a release identity beyond the stated integration. Reuse CI and release tracing for artifact provenance and source data flow for executable input.

1. Establish which pipeline really runs#

Trace merge-request source, configuration revision, execution project and trigger actor independently. Fork pipelines ordinarily run with fork resources. A parent member can run a fork merge-request pipeline in the parent context, using fork-controlled configuration with parent resources and that member's permissions. Inspect the exact settings and approval path. Protected variables and runners have additional requirements, including same-project protected branches; do not infer their exposure merely from parent execution. Re-running an older pipeline can retain its original context. GitLab merge request pipelines

Build one row per job: who edits the YAML, who changes included files, which commit is fetched, which scripts execute, and what the resulting identity can do. Package installation, test discovery and generated scripts are executable inputs even when the visible job looks like a harmless test command.

2. Follow configuration and downstream trust#

Resolve include, extends, component inputs, dynamic child configuration and multi-project triggers before judging a job. Capture the merged configuration through an owner-provided export. For remote includes, compare a pinned expected file with a changed inert fixture; supported include:integrity verifies a SHA-256 digest and rejects a mismatch. A mutable reference is a candidate until the lower-trust editor and privileged consumer are connected. GitLab YAML reference

Track forwarded variables and generated child YAML into the receiving pipeline. A downstream job may interpret a value as a shell command, artifact selector, deployment environment or cloud role. Compare the allowed value and one disallowed synthetic value at that exact sink. Preserve the downstream pipeline's own policy and actor, since upstream success does not establish downstream entitlement. Downstream pipelines

3. Separate token restrictions from secret masking#

A job token is limited by its API capabilities and the triggering user's authority. Review the receiving project's allowlist, feature visibility and any fine-grained restrictions together. Allowlisting a project does not independently grant its users new project permissions. Distinguish currently running jobs from expired-token attempts. Review repository-push settings for the deployed GitLab version rather than assuming every job token is permanently read only. GitLab job tokens

Masked and hidden variables address exposure in interfaces; code already authorized to consume a variable can still misuse it. With a dummy value, identify the exact job receiving it, environment scope and later executable consumer. Never print real CI variables to inventory privileges. GitLab CI variables

4. Reproduce one boundary using inert controls#

Candidate Controlled comparison Required evidence
Fork code enters parent authority Trusted base versus inert fork fixture Configuration SHA, execution project, actor and marker-only result
Job A reaches private project B Allowed integration versus disallowed project/actor Exact synthetic resource, token context and accepted/denied response
Runner leaks previous job state Two owned jobs on a disposable runner Known marker survives across a boundary that promises isolation
Publisher consumes wrong artifact Expected producer versus wrong fixture run Digest, selected run and actual trusted consumption

For runners, inspect executor, host/socket mounts, shared working directories, cache policy and whether private images can be reused by unauthorized jobs. Use one predetermined synthetic marker path and one owned image. A shell executor or privileged container is a risk signal; the demonstrated issue is unintended cross-job access. Do not inspect other users' workspaces. Runner isolation guidance

Local command cheat sheet#

These searches read a local clone and execute no CI jobs. Follow results into referenced files; absence of a match is not coverage proof.

rg --files --hidden -g '!.git' -g '*gitlab*' -g '*.yml' -g '*.yaml' ./repo
rg -n --hidden -g '!.git' -g '*.yml' -g '*.yaml' \
  'include:|extends:|trigger:|forward:|id_tokens:|environment:' ./repo
rg -n --hidden -g '!.git' \
  'CI_JOB_TOKEN|CI_PIPELINE_SOURCE|CI_MERGE_REQUEST|privileged|pull_policy' ./repo

Evidence and completion#

  • Pipeline project, configuration revision and triggering actor recorded separately.
  • Parent/fork and protected-resource assumptions verified for the installed version.
  • Included configuration, downstream inputs and executable consumers traced.
  • Token capability, actor permission and receiving-project policy distinguished.
  • Allowed and denied cases use the same private synthetic resource.
  • Job logs contain no usable credentials; fixtures and runner state are removed.

A report should state the lowest actor that can initiate the chain, required maintainer interaction, exact acquired capability and secure comparison. Intended public artifacts, a rejected token, or deliberate shared runner trust do not establish a private boundary failure.

Azure and GCP: identity, delegation and storage accessApply provider-specific controls for Azure management/data permissions and SAS, plus GCP service accounts, federation and object ACLs.

Open canonical module and reverse references

Investigation sequence
  1. 01Pin provider and auth mode
  2. 02Map management and data grants
  3. 03Trace delegated identity
  4. 04Compare one canary
  5. 05Record propagation and clean up

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Define the provider-specific authority graph#

Keep the shared IAM procedure, storage procedure and authorization matrix. Add the provider's actual identity and permission system before transferring an AWS hypothesis. Use two owned principals, one allowed and one restricted, and one private canary per tenant. Record Azure tenant/subscription/resource IDs or GCP organization/project/resource names, selected CLI account and impersonation settings. An application tenant must not select another tenant's cloud identity or access path.

Azure: distinguish management, data and delegation#

  1. Classify the caller. Record whether the application uses a user, service principal or managed identity. Map application tenant to that principal's object ID and allowed resource. Separate Microsoft Entra directory roles from Azure resource-role assignments. Inspect inherited assignments, groups, conditions and deny assignments using owner exports when visibility is incomplete.

  2. Trace the real blob authorization route. A storage management role is not automatically a Blob data role when using Entra authentication. However, management permission to retrieve account keys can create a different data-access route if Shared Key is permitted. Preserve whether the request used Entra, Shared Key or a SAS; otherwise an apparent RBAC bypass may simply be a different credential. Test only the canary and reuse the identical auth mode for both controls. Azure Blob authorization

  3. Identify the SAS type. Distinguish user delegation, service and account SAS. Record signed resource, allowed operations, validity interval and any stored-access-policy relationship before testing issuance. Ask tenant A for B's owned canary through the application, then verify only the issued capability's permitted synthetic operation. A correctly issued bearer SAS working after application logout is not automatically a flaw. Revocation mechanisms differ by SAS type; compare against the product's promise instead of assuming a uniform logout rule. Azure SAS overview

GCP: distinguish attachment, impersonation and storage grants#

  1. Map service-account selection. iam.serviceAccounts.actAs supports attaching an identity to an executable resource; it does not by itself grant general short-lived token creation. Token Creator permissions are a separate path. Inspect workload-create/update authority, selected service account and that account's actual resource access together. In a disposable fixture, compare one canary-only workload using the normal identity with the same supported operation using a more powerful owned identity. Do not create long-lived keys as a proof step. Service-account permission model

  2. Resolve inherited and object-level access. Record project/folder/organization grants and relevant restrictions. For Cloud Storage, determine whether uniform bucket-level access is enabled. When it is disabled, object ACLs can supply access independently of bucket IAM; when enabled, ACL operations are disabled. Compare the exact object and generation under the same account. A project policy export alone cannot establish every object-access path. Uniform bucket-level access

  3. Inspect workload federation bindings. Verify issuer, audience, attribute mapping, attribute condition and target-resource/service-account binding. For a shared CI identity issuer, bind trust to the intended organization/repository and environment using the provider's documented claim semantics. Compare legitimate allowed and disallowed identities in an owned fixture. A valid issuer signature establishes token origin, not entitlement to every configured workload pool. Federation for deployment pipelines

Bounded command card#

These commands contact provider APIs. Run individually using prepared owned credentials. Supply approved literal IDs; inspect Azure storage environment variables and GCP impersonation configuration first. The Azure policy command avoids Microsoft Graph name lookups, but is still an authenticated management API request. It does not enumerate group membership or prove effective access. Azure role-assignment CLI

timeout 20 az role assignment list \
  --subscription OWNED_SUBSCRIPTION_ID \
  --scope /subscriptions/OWNED_SUBSCRIPTION_ID/resourceGroups/bounty-fixture \
  --assignee-object-id OWNED_PRINCIPAL_OBJECT_ID \
  --include-inherited --fill-principal-name false -o json

env -u AZURE_STORAGE_KEY -u AZURE_STORAGE_SAS_TOKEN \
  -u AZURE_STORAGE_CONNECTION_STRING timeout 20 az storage blob show \
  --subscription OWNED_SUBSCRIPTION_ID --account-name OWNED_STORAGE_ACCOUNT \
  --container-name bounty-fixture --name canary.txt \
  --auth-mode login --only-show-errors -o json

timeout 20 gcloud --configuration=bounty-lab-low \
  storage objects describe gs://OWNED_BUCKET/bounty/canary.txt \
  --format=json

--auth-mode login prevents the Azure CLI's legacy key-lookup route from changing the identity argument. The last two commands request metadata of one known fixture. Repeat under the intended allowed identity; metadata access does not itself prove the file contents were disclosed. A timeout or absent object is not a permission denial. Azure Blob CLI, GCP object describe

Report and cleanup checklist#

  • Provider, tenant/project, principal and selected authentication mode recorded.
  • Management, data access and delegated workload authority separated.
  • Inheritance, ACLs, conditions and visibility gaps documented.
  • Allowed and denied controls address one identical synthetic resource.
  • SAS/federation claims redacted; usable tokens excluded from evidence.
  • Documented propagation delays considered before claiming revocation failure.
  • Disposable workloads, object generations, temporary grants and shares cleaned up.

Preserve the precise failed edge and request IDs. A powerful administrator, intended delegation, stale session or deliberately public object must remain separate from an application tenant escaping its assigned authority.

Resource budgets and sensitive business flowsTrace request fan-out, retries, recipient limits and tenant quotas using tiny local limits, recording stubs and measured downstream effects.

Open canonical module and reverse references

Investigation sequence
  1. 01Name the protected resource
  2. 02Prepare recording stubs
  3. 03Establish limit controls
  4. 04Vary one budget dimension
  5. 05Count committed work
  6. 06Restore and retest

At each step: retain the control → record the observation → continue only while the next boundary is understood.

Identify the unit that must be bounded#

An HTTP request can fan out into database queries, recipient notifications, reservations, image conversions, token generation, or recurring background work. Name the scarce resource and the authority that should own its budget: actor, tenant, recipient, workflow, or global service. A request-per-second limit does not necessarily constrain those downstream units. OWASP distinguishes unrestricted resource consumption from excessive use of sensitive business workflows; both need a concrete product-specific impact. API4, API6.

Build a small cost map before a test. For each entry point record accepted input size, batch cardinality, query/page complexity, queued jobs, retries, output size, and billed provider calls. Include failed requests: validation after a notification or job enqueue can return an error after consuming the protected resource. Use the state and business logic module for one-time transitions and the API protocol module for per-operation behavior.

Controlled procedure#

  1. State the business rule. Specify the prohibited outcome, such as more than two notifications to one synthetic recipient per lab window, or a member reserving an object after their allocation is exhausted. A missing CAPTCHA or rate-limit header alone is not the outcome.
  2. Prepare a faithful local deployment. Replace email, SMS, payments and model providers with recording stubs. Use supported configuration to reduce a real limit to a small lab value when possible. Record the configuration difference. Do not introduce a new guard or a fabricated endpoint and claim it represents the original application.
  3. Establish accepted and rejected controls. Exercise a permitted request and one documented over-limit case. Inspect actual downstream events and final state alongside the response. If the protected limit does not exist, bound the observation through source analysis and an isolated fixture; do not seek a production outage.
  4. Change one dimension. Compare a single operation with a small batch; the same actor through two permitted entry points; an original event with a duplicate; or a queued operation before and after its reservation expires. Keep the recipient, tenant, and resource ownership under your control. Test concurrency only when required to distinguish a non-atomic counter, using a local two-worker harness.
  5. Measure work and isolation. Record request count, accepted operation count, provider-stub count, bytes, task retries, and the budget's key. Check whether tenant A can consume tenant B's allocation or cause B's valid request to be rejected. A shared quota can be intentional; use the stated product rule.
  6. Reset and minimize. Cancel pending jobs, release synthetic reservations, clear only the dedicated fixture's state, and replay the smallest sequence. Recheck that a legitimate operation still works after the counter expires or allocation is restored.

Local command card#

The following is an adaptable request recipe, not a runnable application. Prepare lab/baseline.json, lab/at-limit.json, and lab/over-limit.json using the product's real request schema; create a header file with a synthetic lab token. Replace /api/exports with the reviewed local endpoint. Confirm the application uses recording stubs and that all three cases fit the agreed tiny experiment before running it.

mkdir -p evidence
for lab_case in baseline at-limit over-limit; do
  curl -q --noproxy '*' --proto '=http' \
    --connect-timeout 2 --max-time 10 --max-filesize 65536 \
    --request POST --header @lab/authorization.header \
    --header 'Content-Type: application/json' \
    --data-binary "@lab/${lab_case}.json" \
    --dump-header "evidence/${lab_case}.headers" \
    --output "evidence/${lab_case}.body" \
    'http://127.0.0.1:8000/api/exports' || break
done

This sends at most three sequential requests and does not follow redirects. It does not reset state, enforce the server's budget, or prove a limit. Capture the application's worker and stub logs separately. A transport failure stops the loop and must be recorded as blocked; HTTP error responses remain available for interpretation. If each case requires a fresh counter, restore the fixture between cases instead of running the loop as written. curl options.

Evidence, false positives, and completion#

Keep the expected budget, configuration, identities, input sequence, counter key, downstream effects, and valid control together. Demonstrated local resource amplification may support an impact argument; projected production cost remains an extrapolation until deployment and pricing assumptions are established. A few accepted requests below an unknown threshold do not prove unlimited access. A slow response is not proof of denial of service, and standard product automation may be intended.

  • The protected resource or business allocation and intended owner are explicit.
  • Real integrations are stubbed; no uninvolved recipient, customer, or bill is affected.
  • The accepted control and over-limit control use the same relevant configuration.
  • Downstream work is counted even when the HTTP response indicates rejection.
  • Batch size, retries, async processing, and identity/tenant budget keys are considered.
  • The minimal observation and any cost extrapolation are clearly separated.
  • Pending work and fixture state are restored, with a legitimate operation retested.

Vulnerability reference#

Broken object-level authorization (BOLA / IDOR) · Broken function-level authorization (BFLA) · Mass assignment of server-controlled properties · Authentication or MFA state bypass · Residual access after explicit session revocation · OAuth login or account-linking confusion · JWT signature, issuer, audience, or token-purpose validation failure · OS command injection · Server-side request forgery (SSRF) · Path traversal outside an allowed root · Web cache poisoning · Web cache deception · HTTP request smuggling / desynchronization · Race condition in a security-sensitive transition · Workflow order or approval bypass · Single-use action or idempotency replay failure · Hard-coded product credentials · CI metadata interpreted as executable script · Untrusted contribution executed by privileged CI · CI artifact poisoning across a workflow trust boundary · Dependency confusion and unintended package origin · Predictable temporary files and unsafe creation · Untrusted executable, library, or module search path · Overbroad cloud role trust · Excessive service-role delegation · Federated cloud role accepts the wrong workload identity · Anonymous access to private object storage · Unauthorized object creation or replacement · Signed storage capability exceeds its authorized scope · Private delivery policy bypassed through the origin · Kubernetes Secret access exceeds tenant permissions · Workload creation grants unintended service-account authority · Admission policy misses a supported object or update path · Namespace or workload network restriction is ineffective · Container exposes unintended host files or privileges · Sensitive values exposed through state or plan artifacts · Secrets retained in container build artifacts · Supported deployment default exposes a protected service · Dangling DNS reference to a retired service · Redis authentication or key ACL boundary failure · PostgreSQL trust authentication exceeds the approved network boundary · Message broker permissions cross a virtual-host boundary · Container daemon management endpoint lacks required client authentication · TLS peer certificate or hostname verification failure · Exposed repository or history secret · Untrusted Host or forwarding-header origin construction · SAML assertion signature, audience, or response binding failure · Unauthorized authenticator enrollment, replacement, or removal · WebAuthn assertion accepted for an invalid account or ceremony context · Verification or account merge preserves another actor's access · Enforced SSO bypass through an alternate login path · Argument or option injection without shell interpretation · Parameter or JSON parser disagreement crosses a security check · Server-side parameter pollution in a constructed request · Unverified or tampered webhook causes a protected effect · Authentic integration event applied to the wrong tenant or object · GitLab job token crosses an unintended project boundary · Shared CI runner exposes private state across jobs · Unbounded resource allocation or request amplification · Sensitive business flow exceeds its permitted frequency or allocation

Scope and provider policy#

Record the provider, account/project/subscription ID, tenant or organization ID, region, exact resource identifier, endpoint, permitted principal and excluded adjacent services. For containers, add image repository and digest, runtime, host ownership and allowed execution environment. For Kubernetes, add cluster/context, namespace, service account, controller and control-plane ownership. For IaC, add repository commit, module/provider versions, workspace and whether generated plans or deployed infrastructure are included.

Provider permission and program permission must both cover the activity. A SaaS program can authorize application testing without authorizing customer-owned integrations or the CSP control plane. Conversely, a provider's permission to test your own resources does not make another customer's resources available.

Provider Current operational rule Hunting implication
AWS Listed services may be tested without prior approval; unlisted services require AWS engagement. Testing AWS infrastructure/services themselves is outside this customer policy. C2 needs prior approval. The page explicitly prohibits S3 bucket takeover and subdomain takeover, flooding and specified Route 53 attacks; API Gateway and Bedrock AgentCore are prohibited for outbound penetration testing. Check the current service/activity list. An application's bounty policy cannot override AWS restrictions. Do not claim dangling AWS resources as a default validation step. AWS policy
Microsoft/Azure The unified rules cover owned or explicitly authorized assets. They prohibit third-party data/credential access, including publicly leaked credentials, DoS, excessive automation and post-exploit activity beyond initial identification. They encourage cross-tenant tests using test accounts and require immediate reporting of identified vulnerabilities. Use two owned trial tenants where applicable. Stop at the initial boundary proof; do not enumerate internal files, dump secrets or pivot. Check the separate bounty scope for reward eligibility. Microsoft rules
Google Cloud Prior notification is unnecessary for testing your own projects, provided tests affect only those projects and comply with the AUP and terms. Project identity matters more than a shared IP range. Provider vulnerabilities follow Google's reporting route; customer application findings follow the customer's program. Google Cloud FAQ

These are policy snapshots checked on 2026-09-11, not permanent permissions. Account inventory commands below are ordinary authorized administration; their presence does not imply that every service is approved for penetration testing.

Staged playbook#

1. Establish two controlled identities. Prefer program-provided test credentials or two owned lab tenants, each with synthetic objects. Separate the administrative fixture-creation identity from the low-privilege identity used to reproduce the issue. Record the effective caller before every session. A role called “read only” is not sufficient evidence: inspect permitted actions and data access.

2. Build the authority graph. Trace user → application → workload identity → cloud API → resource. Distinguish management-plane permissions from data-plane permissions; a reader of resource configuration may lack blob access, while a workload can have data access without subscription administration. For AWS, evaluate identity policies, resource policies, boundaries, session restrictions and organization controls together; a single allow statement does not establish effective access. IAM evaluation

3. Follow a business object through storage and jobs. Create one canary document in tenant A and another in B. Exercise upload, preview, export, asynchronous processing, attachment download, sharing and deletion. Compare the authenticated caller with the tenant supplied in URLs, object keys, job messages and signing requests. Inspect authorization at both submission and retrieval: a protected export creation endpoint can still produce an unprotected result URL.

Presigned URLs are bearer capabilities. Sharing one can intentionally grant access until expiry; this alone is not IDOR. Investigate unauthorized URL issuance, a signature covering the wrong object/method, excess lifetime against a documented requirement, or ability to replace a different tenant's object. AWS documents that their usable lifetime also depends on the signing credentials. S3 presigned URLs

4. Inspect Kubernetes boundaries before attempting runtime behavior. Determine whether the promise is namespace separation, virtual control planes, dedicated nodes or dedicated clusters. Namespaces require supporting authorization, networking and storage controls; creating a NetworkPolicy object does not prove enforcement by the CNI. Map ingress, pod egress, persistent volumes and shared controller privileges separately. Kubernetes multi-tenancy

Review workload-creation permission as a capability to select service accounts and mount namespace resources. Inspect bind, escalate, impersonate, token-request and nodes/proxy grants; even a get on nodes/proxy is not necessarily read only. Do not dump Secrets to prove that list reveals their content. Use permission review, supplied policy exports and a deliberately created canary instead. RBAC risks

5. Review admission and controllers as separate authorization layers. Check create/update coverage, resource/subresource matches, exclusions, namespace labels, mutating order and webhook failure behavior. A low-privilege custom resource can be consumed by a powerful operator: inspect whether it can nominate another namespace, secret reference, service account, volume or external destination. In an isolated cluster, compare the requested object, admission decision and controller-produced object. Server dry-run contacts admission components and is not an offline safety guarantee. Admission guidance

6. Trace IaC and container assumptions into deployment. Inspect default values, conditional branches, environment overlays, chart rendering, mutable image references and identity bindings. A repository warning becomes a strong finding when a supported deployment creates an unintended reachable boundary. Pin the image digest, map the vulnerable code to that image, and separate container UID 0 from proven host privilege. Review privileged mode, host namespaces, host mounts and capabilities against the deployment's isolation promise. Pod Security Standards

Terraform's sensitive flag redacts normal display; it does not automatically remove values from state. Review state/plan access and secret lifetimes, while treating any obtained state as sensitive evidence. Never run unreviewed provider binaries or provisioning code with workstation credentials. Terraform sensitive data

Hypotheses, controlled tests and evidence#

The following are test-design recommendations; prerequisites must be satisfied before execution.

Hypothesis Minimum controlled test Evidence and interpretation
Tenant supplied by the client controls the signing identity Ask as A for a signed URL naming B's owned canary, then perform only the permitted canary read A identity, B object ownership, signing request, resulting canary bytes. Ordinary use of a legitimately issued share URL is a negative control.
A queued job loses caller authorization Submit a benign export, revoke A's access to its own fixture, then request the result Job ID, before/after access state, export timestamp and documented revocation semantics. Do not assume immediate revocation is promised.
Fetcher follows an unsafe destination Send one URL to an owned callback; test redirect handling entirely between owned endpoints Unique nonce and callback log prove server-side fetching only. They do not prove metadata access or credential theft. Use a mock metadata service in a local replica for that path.
IAM trust is broader than the integration contract Compare supplied trust conditions and principal restrictions against two owned identities Exact policies and denied/allowed benign operation. Treat missing conditions as a hypothesis until their security consequence is shown.
A controller trusts cross-namespace references In a local cluster, have tenant A reference tenant B's synthetic object through one supported custom resource Custom resource, reconciler identity, generated object and synthetic result; isolate controller behavior from a direct RBAC bypass.
Admission excludes a supported update path Compare normal creation and one equivalent update using a harmless lab workload Both requests, policy match information and final security context. Never deploy a privileged production workload just to strengthen evidence.
Template defaults undermine isolation Render the supported configuration; inspect policy and reproduce with local fixtures Commit, values file, rendered resource, reachable boundary and secure comparison. A linter finding alone is not demonstrated exploitation.

Checklist#

  • Exact account/project/subscription, resource owner and active principal match scope.
  • CSP policy and bounty policy both cover the method; third-party integrations are identified.
  • Test tenants A/B, synthetic objects and baseline denied requests exist.
  • Management plane, data plane, storage signing and background jobs are separately mapped.
  • IAM inheritance and restrictions are considered; exported policy is not mistaken for effective permission.
  • Cluster, namespace, CNI, admission, operator and node boundaries are distinguished.
  • Workload creation and sensitive RBAC verbs are reviewed without reading real secrets.
  • Image/module digest, supported deployment mode and runtime reachability are recorded.
  • SSRF proof uses owned callbacks; impact beyond the callback remains explicitly unproven.
  • The report contains one minimum boundary violation, redacted evidence and cleanup status.

Command cheat sheet#

Use installed official CLIs, your own configured lab credentials and the literal approved IDs. These eight commands read configuration or perform local validation; none grants permission to broaden scope. Do not substitute credentials found in a repository. Protect output files because policies and identities can be confidential.

# 1. Confirm the AWS principal; no secret value is printed.
aws --profile bounty-lab sts get-caller-identity --output json --no-cli-pager

# 2. Inspect one owned general-purpose bucket's policy status.
aws --profile bounty-lab s3api get-bucket-policy-status \
  --bucket YOUR_OWN_BUCKET --expected-bucket-owner YOUR_ACCOUNT_ID \
  --output json --no-cli-pager

# 3. Confirm Azure's selected subscription and tenant.
az account show --query '{subscription:id,tenant:tenantId,user:user.name}' -o json

# 4. Read the policy attached to one owned GCP project.
gcloud projects get-iam-policy YOUR_OWN_PROJECT_ID --format=json

# 5. Ask permission without reading the canary Secret.
kubectl --context bounty-lab -n tenant-a auth can-i get secrets/bounty-canary

# 6. Review only the owned namespace's configuration.
kubectl --context bounty-lab -n tenant-a get roles,rolebindings,networkpolicies -o json

# 7. Inspect a local image without executing its entrypoint.
docker image inspect YOUR_LOCAL_IMAGE --format '{{.Id}} {{json .Config.User}}'

# 8. Validate a trusted, already-initialized local Terraform module.
terraform -chdir=./owned-lab-iac validate -json

Command references: AWS identity, bucket status, Azure account, GCP policy, kubectl permission query, Kubernetes RBAC, Docker inspect, Terraform validate.

Interpret results narrowly: bucket policy status is not a complete audit of all object-access paths; a GCP project policy does not enumerate every inherited control; can-i evaluates authorization, not admission or exploitability; terraform validate checks configuration validity, not security. Terraform may invoke installed provider plugins, so “local” does not mean safe for an untrusted repository.

Provider-specific execution branch#

Use Azure and GCP boundaries alongside the shared IAM and storage procedures. Azure testing must distinguish Entra data roles from management authority, account-key access and SAS. GCP testing must distinguish service-account attachment from token creation, and bucket IAM from object ACLs where those remain enabled. The module includes bounded policy and single-object metadata commands with explicit authentication assumptions.

Map each observed result to its actual credential path. If an SDK or CLI silently changes from user authentication to account keys or an impersonated service account, rerun the comparison with the intended identity before calling it an authorization bypass.

Sources#

All accessed 11 September 2026. Match living documentation to the target version.

Publisher Source Publication or version
Amazon Web Services Penetration Testing Undated
Microsoft Security Response Center Microsoft Security Testing Rules of Engagement Undated
Google Cloud Security FAQ Undated
Amazon Web Services Policy evaluation logic Undated
Amazon Web Services Download and upload objects with presigned URLs Undated
Kubernetes Multi-tenancy Undated living documentation
Kubernetes Role Based Access Control Good Practices Undated living documentation
Kubernetes Admission Webhook Good Practices Undated living documentation
Kubernetes Pod Security Standards Undated living documentation
Kubernetes Using RBAC Authorization Undated living documentation
HashiCorp Manage sensitive data in your configuration Undated
Amazon Web Services get-caller-identity — AWS CLI v2 Command Reference Undated living documentation
Amazon Web Services get-bucket-policy-status — AWS CLI v2 Command Reference Undated living documentation
Microsoft Learn az account Undated living documentation
Google Cloud gcloud projects get-iam-policy Undated living documentation
Kubernetes kubectl auth can-i Undated living documentation
Docker docker image inspect Undated
HashiCorp terraform validate command reference Undated