Mobile
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
- 01Establish permission rule
- 02Seed two controlled identities
- 03Replay one changed dimension
- 04Verify durable effect
- 05Revoke and repeat
- 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
- Capture a working baseline. As A, create private O1 containing
BB-A-ONLY-01; as B create O2 containingBB-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. - 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.
- 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.
- 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.
- 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.
- 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
- 01Map identity states
- 02Capture valid and invalid controls
- 03Alter one binding
- 04Verify account identity
- 05Test revocation
- 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
- 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/meand an intentionally invalid credential form negative controls. - 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.
- 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.
- 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.
- 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.
- 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
activehas 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. - 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
- 01Identify input and sink
- 02Freeze a synthetic fixture
- 03Send paired controls
- 04Observe sink semantics
- 05Confirm smallest effect
- 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
- 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.
- 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.
- 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$eqwith 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. - 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. - 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. - 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.
Browser execution, cross-origin reads, and forged actionsUse real browser behavior to distinguish XSS, CSRF, CORS, and messaging failures from header-only leads.
Open canonical module and reverse references
- 01Locate source and browser sink
- 02Prepare two controlled origins
- 03Establish normal behavior
- 04Run browser-feasible probe
- 05Verify execution or state
- 06Remove stored content
At each step: retain the control → record the observation → continue only while the next boundary is understood.
Setup and invariant
Use two controlled accounts and separate browser profiles. For cross-origin tests, use a researcher page and the local target on different ports; these are different origins but may still be the same site. For conclusions involving SameSite cookies, reproduce with genuinely different sites in an authorized HTTPS setup. Record browser version, cookie attributes, CSP, iframe sandboxing, request initiator, and navigation context.
The invariant is: a lower-trust origin or stored value cannot execute in the application origin, read protected responses, or cause an unauthorized credentialed action. Each of those outcomes is a separate test. XSS, CSRF, CORS.
Numbered test procedure
- Map sources and sinks. Start with a distinctive text marker. Track it through reflected HTML, attributes, JavaScript strings, DOM insertion, Markdown preview, stored comments, embedded documents, and
postMessagehandlers. Record what is escaped and whether rendering occurs server-side or in JavaScript. Do not copy one payload across unrelated contexts. - Build a normal and inert control. As A, save plain text into a private object B is allowed to view. Verify B sees it. An HTML-escaped version is the inert comparison. For a local HTML text sink, a marker-only example is
<img src=x onerror="document.documentElement.dataset.bb='BB-XSS-01'">. Inspect B's DOM for the resulting attribute; the payload never reads cookies or sends data. If CSP blocks execution, preserve that outcome instead of claiming execution. - Test CSRF delivery. Capture B's normal reversible preference change. From the controlled other origin, reproduce it using a browser-deliverable form or fetch request without a manually inserted target bearer token. Verify which cookies were actually sent, whether preflight occurred, and whether the setting changed in a fresh target tab. Use an invalid CSRF token or logged-out profile as a control. A manually edited HTTP request is not by itself a browser exploit.
- Test CORS reads. From the researcher page, call a private test endpoint with
credentials: 'include'and attempt to read the response body. Compare an allowed application origin, the researcher origin, and an anonymous request. Save the actual browser result and credential behavior. A request reaching the server is different from JavaScript reading its response; wildcard origins do not enable credentialed response access. Browser CORS behavior. - Test messages and embedding where present. For an observed
postMessagehandler, send the expected message shape from your controlled page and change only the sender origin. Verify a harmless state marker or a restricted action on your account. For iframe interaction, prove the sensitive action is practically inducible, rather than reporting absent framing headers alone. - Interpret delivery and cleanup. Record which account planted content, which viewed it, the effective origin, and the boundary crossed. Remove stored payloads and reverse preference changes. A payload that only executes after the tester pastes JavaScript into DevTools has not established an application injection path.
Command card
Create browser-probe/index.html locally with this content, then serve it using the command below. Open it while the separate controlled target session exists; confirm that your browser's cookie policy permits the intended experiment.
<!doctype html><meta charset="utf-8"><pre id="result"></pre>
<script>
fetch('http://127.0.0.1:8000/api/me', {credentials: 'include'})
.then(r => r.text())
.then(t => { document.querySelector('#result').textContent = t; })
.catch(() => { document.querySelector('#result').textContent = 'Read blocked or request failed'; });
</script>
python3 -m http.server 8766 --bind 127.0.0.1 --directory browser-probe
Report threshold and checklist
Report cross-user execution, a browser-readable private response, or a forged protected state change with realistic delivery. Reflection, arbitrary Origin acceptance in curl, missing CSRF tokens, and same-site laboratory results generalized to cross-site attacks are common false positives.
- Real browser reproduces the claimed behavior.
- Origin and site relationships recorded separately.
- Credentials, CSP, preflight, and redirects observed.
- Benign marker demonstrates only the required impact.
- Stored content and account settings cleaned up.
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
- 01Map file lifecycle
- 02Seed harmless canaries
- 03Change one file property
- 04Check parser and destination
- 05Verify another controlled reader
- 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
- 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.
- 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/pngshould 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. - 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. - 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.
- 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.
- 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
- 01Write business invariant
- 02Capture normal transitions
- 03Change ordering or replay
- 04Compare serial control
- 05Try bounded lab concurrency
- 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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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
- 01Inventory observed operations
- 02Choose one permission invariant
- 03Compare controlled identities
- 04Test alternate protocol path
- 05Check stream lifecycle
- 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
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.
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.
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.
Check gRPC context. Use the provided
.protofile 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.protoannotation. HTTP/JSON transcoding. gRPC authentication.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-IDselects 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.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.
Source review: entry point to protected operationTrace attacker control and authorization through handlers, services, queues, and sensitive operations; promote only reachable violations of a stated invariant.
Open canonical module and reverse references
- 01State invariant
- 02Find entry
- 03Trace transformations
- 04Check guards
- 05Exercise interface
- 06Retest sibling paths
At each step: retain the control → record the observation → continue only while the next boundary is understood.
Write the invariant first#
Choose a business rule precise enough to falsify: “a member can export documents only from a project where membership is currently active.” Record actor, object, operation, tenant, and when authorization must hold. A suspicious function becomes useful when you identify who can reach it and what protected state it changes. Reuse the authorization matrix for principal/object combinations and injection tracing for interpreter-specific input handling.
Start forward at a real interface: HTTP route, websocket event, CLI argument, uploaded project, plugin callback, or queue producer. Then work backward from the sensitive read, write, process invocation, parser, or credential use. Join the two traces; unresolved dynamic dispatch is an explicit gap. Check route registration and feature gating rather than assuming a named handler is exposed.
A worked asynchronous trace#
The following is a hypothetical review worksheet, not a finding against a named project.
| Hop | Value or context | Question to resolve |
|---|---|---|
POST /exports |
Authenticated member A supplies document_id |
Does the route check document membership or only login? |
ExportService.create |
A job stores document_id, requested_by |
Is the trusted tenant copied from the session or accepted from input? |
| Queue serialization | Job survives after request completes | Can another low-trust producer alter the fields? |
run_export(job) |
Worker reloads document by ID | Is current membership enforced at execution time? |
| Object storage write | Export receives a download reference | Who can fetch it, and can authorization expire? |
A useful candidate is “worker rehydrates an object globally after a tenant-scoped request.” Confirm whether the earlier guard binds the same immutable identifier, whether membership revocation should affect queued jobs, and whether the output is actually accessible. Business requirements may deliberately snapshot permissions; document that distinction rather than treating every delayed check as defective.
Local search and modeling cheat sheet#
Examples inspect a reviewed local repository. Patterns are leads, not a complete inventory. Place evidence outside the scan root; prepare local rules before offline/private-code analysis.
rg -n --hidden -g '!.git' \
'route\(|router\.|websocket|add_task|enqueue|\.delay\(' ./repo
rg -n --hidden -g '!.git' \
'pickle\.loads|yaml\.load|subprocess\.|child_process|exec\(' ./repo
semgrep scan --oss-only --config ./rules --metrics=off \
--disable-version-check --json \
--output ./evidence/source-leads.json ./repo
The Semgrep command uses reviewed local rules and the OSS engine. Inspect stderr, skipped files, and scan completion; an empty result with parser failures is not a clean audit. Rule licenses and engine capabilities are separate concerns. Semgrep CLI reference.
Model sources, propagation, context-specific sanitizers, and sinks. A path validator does not sanitize a shell argument; a role check does not validate serialized data. Custom wrappers can hide a source or sink from default rules. Create one vulnerable fixture and one safe sibling to validate your model. Static taint analysis does not automatically establish tenant policy or full runtime reachability. Semgrep taint model.
Prove the boundary through supported use#
Instrument the local application to log identifiers, decisions, and function names without credentials. Reproduce from the ordinary interface with synthetic accounts. Keep four observations together: allowed own-object request; denied or expected-to-deny foreign-object request; actual protected effect; resulting output access. Repeat without instrumentation changing security behavior. If only an internal helper is callable, qualify the finding as a library/caller-contract issue until a consumer path is established.
Inspect alternate callers after proving one flaw: bulk API, export service, websocket subscription, scheduled task, and administrative UI may reuse the same operation with different guards. Group equivalent paths by root cause and record tested variants. Do not inflate one shared defect into five independent findings.
Completion and report threshold#
- Invariant identifies actor, object, operation, and trust boundary.
- Attacker-controlled field is tracked through transformations and aliases.
- Every guard is evaluated with concrete fixture values.
- Feature, edition, role, and configuration prerequisites are explicit.
- Real interface reproduces the protected effect beside an authorized control.
- Safe sibling and patch retest preserve legitimate behavior.
Submit the smallest connected argument: entry point → attacker control → ineffective boundary → sensitive operation → demonstrated impact. A sink search, scanner trace, or direct helper call alone belongs in the hypothesis queue.
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
- 01Identify event trust
- 02Map executable input
- 03Inspect privileges
- 04Follow artifacts
- 05Validate locally
- 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
- 01Inventory layers
- 02Resolve advisory
- 03Trace consumer
- 04Check prerequisites
- 05Compare fixed behavior
- 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.
Libraries and parsers: bounded fuzzing to a minimal reproducerBuild a realistic local harness, instrument the target, bound fuzzing resources, and turn unique faults into defensible library or consumer reports.
Open canonical module and reverse references
- 01Choose contract
- 02Build harness
- 03Instrument target
- 04Fuzz within bounds
- 05Minimize root cause
- 06Verify consumer impact
At each step: retain the control → record the observation → continue only while the next boundary is understood.
Choose an attacker-relevant parser contract#
Select a public API and a realistic input boundary: imported image, archive, document, packet, model file, project metadata, or CLI input file. Record who supplies bytes, the consumer's preprocessing, format limits, parser options, and the privilege of the process. Fuzzing an internal helper with impossible arguments may reveal a harness defect rather than a violated library contract.
Keep the first harness narrow: one parse operation, deterministic configuration, fresh state for each input, bounded allocations, and cleanup on both success and failure. Remove network and uncontrolled filesystem side effects. Seed with a few valid, small representative samples to reach deeper code; an all-random corpus may spend every iteration failing the magic-header check.
Harness and build cheat sheet#
The following is a template: product_parser.h, ProductDocument, and the parser functions are placeholders to replace with the target's documented API. Return/error ownership must follow that API. The commands have not been executed here.
#include <cstddef>
#include <cstdint>
#include "product_parser.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size > 65536) return 0;
ProductDocument* doc = product_parse(data, size);
if (doc != nullptr) product_document_free(doc);
return 0;
}
Build the target library itself with compatible sanitizer and coverage instrumentation; instrumenting only the harness misses much of the parser's behavior. Replace the source list with its reviewed build recipe. The example assumes Clang and a compatible C++ project. AddressSanitizer build guidance.
clang++ -g -O1 -fno-omit-frame-pointer \
-fsanitize=fuzzer,address,undefined -fno-sanitize-recover=undefined \
./fuzz_parser.cc ./parser.cc -o ./fuzz_parser
mkdir -p ./corpus ./artifacts ./evidence
./fuzz_parser ./corpus -max_total_time=120 -timeout=2 \
-rss_limit_mb=512 -max_len=65536 -artifact_prefix=./artifacts/
Add reviewed seed files before running. libFuzzer's time, input-length, timeout, and RSS settings bound the campaign; they are not a VM resource boundary. Run inside your local disposable environment with external memory/CPU limits appropriate for the workload. Track new coverage and distinct root causes rather than raw executions. libFuzzer reference.
Interpret the failure precisely#
| Observation | Immediate question | Defensible first claim |
|---|---|---|
| ASan heap read overflow | Does the public API permit this byte sequence? | Out-of-bounds read in the tested build |
| ASan write overflow | Which length check and allocation disagree? | Out-of-bounds write; execution impact unproven |
| Use-after-free | Did the harness violate ownership rules? | Lifetime defect if public usage is valid |
| UBSan arithmetic finding | Does overflow reach an unsafe allocation/index? | Undefined operation plus traced consequence |
| Timeout or RSS limit | Is it reproducible at modest input sizes? | Bounded evidence of resource exhaustion |
-fno-sanitize-recover=undefined makes the selected UBSan failures terminate instead of allowing a fuzzing run to continue after a diagnostic, which helps preserve reproducible failing inputs. UBSan checks selected undefined behaviors, not every arithmetic error. Unsigned integer wrap is defined in C/C++ and is not part of the default undefined group; choose additional checks when the target's threat model requires them. UndefinedBehaviorSanitizer.
Minimize and verify the consumer#
Copy one candidate into ./artifacts/repro.bin, then minimize with a unique output path. Preserve the original input, toolchain, build flags, revision, and symbolized trace.
./fuzz_parser -timeout=2 -rss_limit_mb=512 ./artifacts/repro.bin
./fuzz_parser -minimize_crash=1 -max_total_time=60 -timeout=2 -rss_limit_mb=512 \
-exact_artifact_path=./artifacts/minimized.bin ./artifacts/repro.bin
Replay the minimized bytes through a supported consumer in the pinned OSS lab. If the consumer rejects them before calling the library, report the library contract separately and leave consumer exposure unproven. A sanitizer crash is not proof of remote code execution, and its absence in an optimized build does not erase a confirmed memory-safety defect.
Hypothetical root cause: a header count is multiplied into a narrow allocation size, then a wider loop trusts the original count. Preserve the values, allocation site, failing access, and missing bound. The fix should bind accepted count to available input and checked allocation size; merely suppressing the crash is insufficient.
- Public API and ownership contract respected.
- Target code, not only harness, instrumented.
- Resources, seeds, revision, and toolchain recorded.
- Minimal input reproduces the same root cause.
- Normal input succeeds in affected and fixed builds.
- Library impact and real consumer exposure are stated separately.
Replay timeout and memory artifacts under the same resource limits as discovery. A minimized crash must retain the original root cause; a shorter input that fails differently is not equivalent evidence.
Android components, intents and deep linksTrace ordinary-app inputs through exported components, URI grants and links to one protected operation.
Open canonical module and reverse references
- 01Fingerprint release
- 02Map caller authority
- 03Record valid baseline
- 04Change one input
- 05Verify boundary
- 06Restore lab
At each step: retain the control → record the observation → continue only while the next boundary is understood.
Setup and invariant#
Use an owned emulator/device, the scoped release APK and relevant splits, SDK Platform Tools, APK Analyzer, and an ordinary companion application you control. Seed a private record BB-ANDROID-01 and a deliberately shareable record. Keep ADB for orchestration and observation; its shell identity is not equivalent to an ordinary installed app.
Invariant: an external caller receives only the component actions and URI grants deliberately delegated to that caller. A public activity must still enforce the sensitive operation's requirements. Export defaults vary across components and platform versions, so inspect resolved package state as well as the manifest. Android component exposure.
Procedure and command sheet#
- Fingerprint. Record package, version, target SDK, device API, signer, and release/debug state. These commands inspect a local APK and one selected device:
adb devices -l
adb -s DEVICE_SERIAL shell pm path com.example.lab
adb -s DEVICE_SERIAL shell dumpsys package com.example.lab
apkanalyzer apk summary ./app.apk
apkanalyzer manifest print ./app.apk
apkanalyzer manifest permissions ./app.apk
Use the printed manifest to create rows for activity/service/receiver/provider, export state, permission, URI authority, grant flags, external input, and protected operation. ADB, APK Analyzer.
Establish a baseline. Exercise the intended share or navigation action. Record exactly which URI, recipient, and capability were granted. From the companion app, request only the seeded record through
ContentResolver; retain the result or exception. A provider may require permissions or a temporary grant even when externally addressable. Provider access model.Run one negative control. Repeat without the grant, with the second seeded URI, or from a fresh ordinary caller. The control must change only one condition. For a link, compare normal login with logged-out and account-switched states. ADB can launch a harmless route for diagnosis:
adb -s DEVICE_SERIAL shell am start -W -a android.intent.action.VIEW -d 'labapp://item/BB-ANDROID-01' com.example.lab
Use the actual discovered scheme/package, and repeat any result from the companion app. If links reach the backend, reuse the authorization matrix and identity/session experiments. Link verification does not validate arbitrary parameters. Deep-link risks.
Inspect delegation. Trace nested intents, forwarded extras, and obtained PendingIntent tokens. Determine which action/destination remains caller-controlled. Check one harmless alternate destination or repeat of an intended single-use action. Record OS behavior: Android 16 adds default intent-redirection hardening, so reproduce on the supported version rather than extrapolating from an older emulator. Intent redirection, PendingIntent delegation.
Decide and retain evidence. A report needs caller identity/permissions, precise URI or intent, eligible build, expected denial, and the protected result. An exported activity opening a public screen, a denied request, or access obtained only after granting the attacker target-equivalent privilege is not this finding. Route path handling into file boundaries, and WebView-origin questions into browser trust.
Completion and cleanup#
- Compare intended recipient, unauthorized caller, and revoked-grant state.
- Repeat the smallest result without ADB-only privileges.
- Distinguish data reads, harmless navigation, and state-changing operations.
- Record minimum OS, target SDK, interactions, and ownership assumptions.
- Revoke temporary shares, remove companion-app fixtures, and restore the emulator snapshot after exporting redacted evidence.
Native authentication continuation#
Treat OAuth routing and authorization as separate decisions. Record the external authorization UI, registered callback, app instance, pending transaction and PKCE verifier lifecycle. With two owned local login attempts, compare a normal return with a callback delivered to the other pending attempt. Then compare the correct verifier with the other attempt's verifier at the local token endpoint. A intercepted code that cannot be redeemed is a protected control, not account takeover. Use federation/token trust for issuer, code, state and token decisions. Native public clients require PKCE; an embedded shared client secret is not a confidential per-installation secret. RFC 8252, PKCE.
For passkeys, follow Credential Manager's result into the relying-party server; the credential-selection UI does not finish server authentication. Reuse authenticator lifecycle for challenge, credential/account binding and required verification flags. Record the release signer and native origin/association configuration; do not infer the production origin allowlist from a re-signed APK. Android separates the client, credential provider and verifying server. Credential Manager architecture.
rg -n 'code_verifier|code_challenge|redirect_uri|CredentialManager|PublicKeyCredential' ./source
- Callback receiver and backend authenticated identity are both observed.
- Cross-attempt, expired-attempt and valid-attempt controls use fresh codes.
- Re-signing, debug credentials and altered app links are disclosed.
iOS signing capabilities, links and protected dataConnect entitlements and application groups to actual callers, URL transitions and protected key use.
Open canonical module and reverse references
- 01Identify signed build
- 02Map capabilities
- 03Observe normal access
- 04Change caller or lock state
- 05Verify release result
- 06Clean fixtures
At each step: retain the control → record the observation → continue only while the next boundary is understood.
Setup and invariant#
Prepare a Mac with Xcode tooling and an owned supported device. Record bundle ID, build, signing team, distribution channel, OS, and artifact hash. Keep device IPA, simulator build, enterprise build, and re-signed copy separate. Create two synthetic accounts and one private document whose marker can be recognized without collecting other data.
Invariant: links and extensions cannot borrow capabilities outside their intended trust relationship, and sensitive data/key use respects the promised authentication and lock state. Entitlements describe granted capabilities; they do not demonstrate that an unrelated app can exercise them. Apple enforces application-group relationships through signing and sandbox rules. App protection and groups.
Procedure and command sheet#
- Inspect the unchanged bundle. On macOS, inspect the main app and repeat for each embedded extension. The entitlement command writes to stdout; it does not re-sign the app.
plutil -p ./Payload/Lab.app/Info.plist
codesign -dv --verbose=4 ./Payload/Lab.app
codesign -d --entitlements - ./Payload/Lab.app
Capture CFBundleURLTypes, associated domains, App Groups, Keychain access groups, extension points, and ATS exceptions. A capability missing from Info.plist may be in the signature, so inspect both. OWASP entitlement extraction, OWASP plist tooling.
- Build a capability table. Use columns for main app, extension, signing identity, shared container, shared secret, intended consumer, and external input. In source-available work, locate storage and callback code:
rg -n 'SecItem(Add|CopyMatching)|kSecAttrAccessible|kSecAttrAccessGroup|LAContext|evaluatePolicy' ./source
rg -n 'openURL|continueUserActivity|WKScriptMessageHandler|containerURL' ./source
Matches identify implementation paths, not vulnerabilities. Trace the actual access group/class and the operation that uses the retrieved key; do not infer a security boundary from a biometric-looking screen alone.
Compare link transitions. Open one normal universal link and one custom-scheme route using an owned Notes/browser fixture. Repeat while logged out, after account switching, and with a mismatched synthetic resource. Record browser/app routing separately from account authorization. Universal links require website/app association; custom schemes have different ownership guarantees. For OAuth callbacks, reuse identity/session tests; for object access, use the authorization matrix. Apple universal-link model.
Test one protected-state transition. Establish legitimate access when unlocked, then lock the device and exercise only the supported background/extension path. Compare with a denied access class or an operation requiring fresh user presence. Record whether sensitive plaintext or usable key operations become available, not simply whether encrypted bytes remain on disk. Keychain accessibility, synchronization, and user-presence policy serve different purposes. Keychain data protection.
Confirm the caller model. Test extension input with a harmless imported document or URL through the normal share sheet. Reuse file boundaries and browser trust when files or WKWebView messages are involved. An attacker app signed with the target's team or a jailbreak granting container access invalidates an ordinary-app claim; disclose such constraints and seek an unchanged-build reproduction.
Completion and cleanup#
- Preserve exact build, signature, device state, input and expected denial.
- Explain why background access violates a product promise rather than an intended feature.
- Keep proof to the seeded document or one reversible operation.
- Remove synthetic links/documents and testing profiles; revoke lab sessions and restore the owned device configuration.
Native authentication continuation#
For browser-based sign-in, record the actual authentication session, callback scheme or associated-domain route, and the pending login or account-linking transaction. In the local identity fixture, begin independent A/B attempts and return A's callback while B is pending. Compare the resulting account and token decision with an untouched control. Route matching alone does not establish that a callback belongs to the current transaction. Reuse federation/token trust for the complete code/PKCE/issuer checks; RFC 8252's native-client model separates the external authorization agent from the native client. OAuth for native apps.
For passkeys or local authentication, record whether the protected operation is a remote session issuance or local use of a protected key. Trace the returned credential to the server and apply authenticator lifecycle. A successful Face ID prompt is not evidence that server-side challenge and account binding are correct. Inspect local paths without changing signing identities:
rg -n 'ASWebAuthenticationSession|ASAuthorization|code_verifier|redirect_uri|SecKeyCreateSignature' ./source
- Record callback routing, pending transaction and final account separately.
- Exercise cancellation/expiration and an account switch with synthetic identities.
- Preserve the valid control and confirm no useful credential is issued for the mismatched transaction.
Client storage, logs, backup and transport trustMeasure where sensitive client state goes, who can retrieve it, and whether transport rejects invalid peers.
Open canonical module and reverse references
- 01Define secret and reader
- 02Capture lifecycle baseline
- 03Observe exposure path
- 04Run trust controls
- 05Validate impact
- 06Remove lab material
At each step: retain the control → record the observation → continue only while the next boundary is understood.
Setup and invariant#
Use a clean owned device or desktop VM with the scoped release, two synthetic accounts, a scoped interception setup, and a unique marker such as BB-CLIENT-01. Define the prospective reader: another ordinary app, another OS user, someone receiving an exported diagnostic bundle, an authorized backup recipient, or a network adversary. A debugger/root reader is a separate model.
Invariant: credentials and private records do not enter a channel readable by an unintended principal, and the release rejects an unauthenticated network peer. Prioritize actual token/data movement; a string called api_key, a backup-enabled manifest, or absent certificate pinning alone is insufficient.
Procedure and command sheet#
Record the lifecycle. Start logged out; sign in, perform one marked operation, background/lock, switch accounts, then log out. For each state record relevant preferences, databases, caches, clipboard, notifications and user-exportable diagnostics. Use identity/session for token validity and revocation, and authorization checks for backend effects.
Locate only relevant sinks. Search a local source copy and explicitly acquired lab exports. The bounded file-size limit reduces accidental scanning of large databases; use filenames first when output may contain secrets.
rg -n 'Log\.|NSLog|os_log|console\.log|SharedPreferences|UserDefaults' ./source
rg -l --max-filesize 2M 'BB-CLIENT-01' ./lab-export
apkanalyzer manifest print ./app.apk
On Android, inspect allowBackup, fullBackupContent, dataExtractionRules, and the referenced XML. Test the supported restore path using synthetic data. Cloud backups and device transfers can follow different rules; neither follows reliably from one flag across all versions/OEMs. Do not use a legacy adb backup recipe as a universal modern test. Android backup rules.
Prove reader access. If the marker appears in logs, show who can obtain that release log under the stated prerequisites. Modern log access is restricted; ADB output does not prove every app can read it. If a diagnostic export includes an owned token, demonstrate the expected exclusion with an otherwise identical export that lacks a session. Share a redacted excerpt and file location, not a full personal log. Android log disclosure.
Use a transport control matrix. In a local endpoint or approved test environment, compare a valid certificate, an untrusted issuer, and a wrong-host certificate. Keep request content identical and send only synthetic data. Record the release's exact trust store, endpoint, handshake result and whether an application request is sent. A refusal caused by connectivity is not validation evidence; the valid control must connect. Custom trust code can mistakenly accept invalid chains. Unsafe trust managers.
Separate lab changes. A user-installed CA, debug-only trust anchor, disabled trust callback, or instrumentation can make interception intentional. Android debug overrides may bypass pins when their debug CA is used; they are ignored for a non-debuggable build. Therefore verify bad-peer acceptance on the unchanged eligible release before reporting a certificate-validation defect. Network security configuration.
Evidence, decisions and cleanup#
Retain state before/after, sensitivity, exact reader privileges, minimum exposed marker, and reproducible impact. Intended account-owner backup access and approved offline storage are not automatically vulnerabilities. Use file boundaries when exposure depends on permissions or export paths.
- Compare account A, account B, and logged-out state.
- Demonstrate the reader without granting extra privileges.
- Distinguish missing pinning from accepting invalid certificates.
- Remove test CAs/proxies, delete synthetic exports, clear test clipboard values, revoke owned sessions, and restore the lab snapshot.
Privacy surfaces and metadata-only leakage#
Add a reader-specific matrix instead of reporting absent UI flags. Seed one private message BB-PRIVATE-VIEW and a public title. Compare unlocked/locked, foreground/background and A/B account-switch states. Inspect the app switcher snapshot, lock-screen notification preview, clipboard, accessibility text and a diagnostic export through supported OS interfaces. Record the precise reader privileges, user settings and platform version. A photograph taken by an already authorized user or access after explicitly granting a privileged accessibility service is a different threat model. MASVS-STORAGE-2 covers unintended sensitive-data leakage; product guarantees and eligibility determine the report. OWASP storage leakage control.
For analytics/telemetry, place a synthetic marker in a private field and inspect only the lab app's own traffic or configured local sink. Compare the documented consent states and identify the receiving component and fields. Do not test the analytics vendor. A tracking SDK or a privacy-label mismatch alone does not demonstrate the claimed access boundary. OWASP privacy minimization.
For a timing/size/existence hypothesis, first implement it locally using a small blinded set of synthetic present/absent records. Randomize order, keep request sizes and authentication equal, retain every sample, and evaluate the prediction on held-out fixtures. Show what private fact is inferable, from which observable, with what error rate. Ordinary latency variation, a single paired sample and a marker already supplied in the query establish no disclosure. This is an experimental design to investigate a suspected side channel, not evidence that one exists.
- Private content is actually available to the stated unintended reader.
- User consent, OS grants and already-delivered copies are accounted for.
- A metadata-only claim reports false positives and fresh validation cases.
- Remove notifications, clipboard markers, exports and synthetic analytics data.
Update signatures, rollback and privileged installationTest the update decision and installation boundary while preserving production signer and privilege assumptions.
Open canonical module and reverse references
- 01Observe genuine update
- 02Identify policy and consumer
- 03Build local controls
- 04Test rejection points
- 05Confirm minimum effect
- 06Restore clean state
At each step: retain the control → record the observation → continue only while the next boundary is understood.
Setup and invariant#
Use a disposable installation, the eligible release, one genuine update, a standard-user attacker account and a separate administrative setup account. For source-available projects, create a local updater harness or documented test feed that exercises the real verifier. Record any configuration change: redirecting production traffic or granting the attacker an update signing key would change the claim.
Invariant: installation consumes exactly the artifact authorized by the expected publisher policy, and lower-privileged users cannot substitute content before a privileged consumer uses it. Version rollback is a defect only when it violates a defined security policy or restores a demonstrable vulnerability under realistic attacker control.
Procedure and command sheet#
- Observe a genuine update. Record installed version, requested channel, feed metadata, downloaded filename/hash, expected publisher identity, temporary path, verifier result and installing process identity. Inspect local artifacts without executing them:
apksigner verify --verbose --print-certs ./update.apk
codesign -dv --verbose=4 ./Update.app
codesign --verify --deep --strict --verbose=2 ./Update.app
The Apple commands require macOS; the APK command requires Android build tools. A valid signature establishes properties checked by that tool, not that the product updater enforces its intended publisher. APK signer, Apple code-signing inspection.
Identify the actual updater. Read the shipped update library/version and packaging configuration. Electron update behavior depends on platform and package mechanism; a generic claim about all Electron applications is too broad. Android platform update signing is separate from application-managed downloadable modules or scripts. Electron autoUpdater, Android application signing.
Build three local controls. Use a genuine authorized artifact as the positive control, a one-byte-corrupted copy as the integrity control, and an artifact signed by an unrelated lab publisher as the identity control where the harness supports it. Keep the latter inert. Change only the relevant input and record the precise rejection/acceptance decision. Acceptance of metadata alone does not establish installation; distinguish download, verification, staging and activation.
Inspect the privilege boundary. Determine who can write the staged artifact, parent directory, updater configuration and helper executable. On Windows, these commands inspect signatures and ACLs:
Get-AuthenticodeSignature -FilePath 'C:\Lab\Update.exe' | Format-List
icacls 'C:\Program Files\ExampleApp'
icacls 'C:\Lab\Update.exe'
Use the actual observed staging path, not the illustrative path above. A broad ACL matters only if the standard user can control bytes that a more privileged process actually consumes. Demonstrate a harmless marker operation in a lab path; keep service identity and write access in the evidence. Authenticode inspection, ACL inspection.
- Test rollback semantically. Compare a supported older genuine release with the current release in the local harness. Check whether the defined version policy rejects it and whether an attacker can supply it without administrative or signing authority. Do not force an ADB downgrade and report the explicitly requested platform behavior as an updater bypass. Reuse file boundaries for staging/extraction and identity/session when a rollback changes credential protections.
Completion and cleanup#
- Record exact accepted bytes and identity, not just a green verification UI.
- Preserve genuine, tampered, wrong-publisher and applicable rollback results.
- Explain prerequisite control, installing privilege and demonstrated effect.
- Label locally modified verifier behavior as a limitation until release equivalence is established.
- Restore original feed/configuration, remove inert fixtures, restore the VM snapshot and confirm the normal update path works.
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
- 01Record assurance policy
- 02Enroll controlled factors
- 03Compare fresh and stale proof
- 04Check assertion bindings
- 05Replace and recover
- 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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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
- 01Map issuers and clients
- 02Capture authorized flow
- 03Alter one binding
- 04Verify token semantics
- 05Check replay and linking
- 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
- 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
statealone 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. - 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.
- 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. - 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.
- 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.
- 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.
Vulnerability reference#
Broken object-level authorization (BOLA / IDOR) · Broken function-level authorization (BFLA) · Broken property authorization: private response fields · Mass assignment of server-controlled properties · Authentication or MFA state bypass · Session fixation across login · Residual access after explicit session revocation · Password reset binding or single-use failure · OAuth login or account-linking confusion · JWT signature, issuer, audience, or token-purpose validation failure · SQL injection · NoSQL operator or query injection · Reflected or stored cross-site scripting · DOM-based cross-site scripting · Cross-site request forgery (CSRF) · CORS private-data exposure · Cross-origin messaging trust failure · Unsafe upload interpretation or placement · Path traversal outside an allowed root · XML external entity injection (XXE) · Race condition in a security-sensitive transition · Workflow order or approval bypass · Single-use action or idempotency replay failure · GraphQL resolver or field authorization failure · WebSocket message or subscription authorization failure · Cross-site WebSocket hijacking · Hard-coded product credentials · CI artifact poisoning across a workflow trust boundary · Dependency confusion and unintended package origin · Unsafe object deserialization · Out-of-bounds memory read · Out-of-bounds memory write · Use after free in a public API or parser lifecycle · Integer overflow affecting allocation or bounds · Parser resource exhaustion and decompression amplification · Regular-expression denial of service · Predictable temporary files and unsafe creation · Exported Android component exposes a protected operation · Content-provider permission or URI-grant escape · Nested intent redirects application authority · PendingIntent grants unintended mutable or replayable authority · Mobile link handler trusts routing as authorization · Untrusted WebView content reaches a native bridge · Keychain accessibility violates the intended lock boundary · Extension input crosses an iOS shared-container boundary · Local authentication does not gate the protected operation · Backup or transfer exposes unintended sensitive client state · Sensitive client state leaks through accessible diagnostics · TLS peer certificate or hostname verification failure · Updater accepts an artifact outside its publisher trust policy · Attacker-controlled rollback restores a prohibited security state · Open redirect across an origin trust boundary · Predictable security token or capability identifier · Password storage without an adequate adaptive hash · Encrypted data accepted without integrity authentication · 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 · Private client content leaks through a lower-trust display or sharing channel
Scope and learning route#
Mobile bounty work means testing an explicitly identified application and its reachable trust boundaries. Record the Android package ID or iOS bundle ID, store URL, publisher, signing identity, version/build number, acquisition date, artifact SHA-256, OS version, architecture, and distribution channel. Include Android split APKs and dynamic features; an App Bundle is a publishing artifact, not necessarily the exact installed package set. Keep production, TestFlight, enterprise, debug, and locally compiled OSS builds distinct. A repository commit and a store build are separate evidence coordinates.
Treat backend domains, identity providers, analytics vendors, embedded SDKs, companion apps, and operating-system components as independently scoped assets. A mobile package listing does not automatically authorize arbitrary testing of every URL recovered from it. Likewise, a hostname containing “mobile” in a discovery list establishes neither app identity nor testing authorization. For OSS, reproduce against a locally built release or documented self-hosted backend; do not use maintainers’ demo infrastructure unless its policy includes it.
Use MASVS to organize required security properties, MASTG for testing techniques, and MASWE to name weaknesses. They serve different purposes; a control failure is not automatically bounty-eligible. Record the exact taxonomy version or retrieval date, since identifiers and test mappings evolve. OWASP MASVS, MASTG, MASWE.
Recommended progression: package anatomy and platform permissions → storage and session lifecycle → exported interfaces and deep links → WebView bridges → native libraries and release differences. Advance when you can show an attacker-controlled entry point reaching a protected operation, explain the required privileges, and reproduce the result without changing the security property under test.
Lab prerequisites#
For Android, use an owned device or emulator, Android SDK Platform Tools, APK Analyzer/build tools, JADX, Apktool, and a scoped interception proxy. Start with an ordinary release installation; keep a separate instrumented image for tracing. Match API level, ABI, target SDK, split packages, and relevant Google-service dependencies. Decompiler output is a navigation aid: verify ambiguous control flow against bytecode, original source, or runtime behavior. Android SDK tools, JADX.
For iOS, use a Mac with compatible Xcode tooling and an owned supported iPhone/iPad when device behavior matters. A simulator needs a simulator-compatible build; a normal device IPA is not interchangeable with one. Obtain source, symbols, or an authorized test build where available. Jailbreaking, repackaging, changed entitlements, and instrumentation alter assumptions and must be disclosed; unsupported hardware or unavailable binary access can limit coverage. OWASP iOS Security Testing.
Create two owned application accounts where allowed, seed recognizable synthetic records, and capture an untouched baseline. Keep backend traffic within the explicit scope. An interception certificate installed for the lab changes the trust environment: distinguish expected trust of that certificate from an app accepting invalid certificates without an approved trust anchor. Android debug trust overrides may deliberately change pinning behavior and are ignored in non-debuggable builds. Android network security configuration.
Android playbook#
- Establish provenance. Inventory version, signature, complete package set, minimum/target SDK, and release flags. Gate: the artifact corresponds to the scoped release, or the report clearly limits its claim to another eligible build.
- Map external inputs. Enumerate activities, services, receivers, providers, intent filters, URI grants, custom permissions, and dynamically registered interfaces. Trace inputs to sensitive reads, writes, navigation, account operations, and file handling. Gate: a reachable input exists under the proposed attacker privileges. An exported component may be intentional; export status alone proves no unauthorized operation. Android exported components.
- Exercise one boundary at a time. Use a minimal companion test app to model an ordinary installed application. Check permission enforcement, caller identity, grants, and object ownership. ADB is useful for observation, but its shell identity may have privileges unavailable to an ordinary app. Gate: repeat the finding under the claimed attacker identity with synthetic data. Content-provider access combines URI identification with provider permissions and grants. Android content providers.
- Trace links and embedded content. Follow deep-link parameters through redirects, route selection, authentication gates, and WebView loading. Examine nested frames and native bridge exposure. Gate: untrusted input reaches protected functionality; merely opening a public screen is expected behavior. Verified links bind routing but do not replace authorization or input validation. Android deep links, WebView native bridges.
- Observe lifecycle and trust. Compare storage, logs, caches, clipboard, notifications, backups, and tokens before login, after account switching, and after logout. Trace key use and local authentication rather than collecting every string resembling a secret. Gate: identify sensitive material, its actual exposure path, and an attacker who can access or reuse it.
- Validate release impact. Reproduce the smallest meaningful result on the eligible build, with preconditions and negative controls. Report only the supported chain; label any backend conclusion requiring separately authorized testing as unverified.
iOS playbook#
- Fingerprint the application. Record bundle/version identifiers, signing team, provisioning context, architecture, embedded extensions, and entitlement set. Review
Info.plist, associated domains, App Groups, Keychain groups, URL types, document handlers, and ATS configuration. Gate: the inspected artifact and runtime installation match. - Model shared capabilities. Trace app-to-extension sharing, document imports, pasteboard access, URL handling, and WebView message handlers. Identify which sender or origin the app trusts. Gate: reproduce with a separately controlled input, without granting that input the target’s signing identity.
- Test URL transitions. Cover logged-out, logged-in, expired-session, and account-switch states using harmless links and local fixtures. Verify sensitive operations still authorize the current account. Universal links use website/app association; custom schemes have different routing guarantees. Neither makes arbitrary URL parameters trustworthy. Apple universal-link model.
- Inspect data protection semantically. Determine Keychain accessibility class, access group, synchronization, user-presence constraints, and whether sensitive operations use the protected key. Test lock/unlock and logout behavior against the product’s intended offline/background capabilities. Gate: demonstrate a violation of the intended access boundary, not simply that a secret exists while its owner is authenticated. Apple Keychain data protection.
- Trace network and native paths. Examine trust-delegate behavior, ATS exceptions, authenticated WebViews, callback handling, imported files, and bundled native parsers. Use instrumentation to locate a cause, then separate observations introduced by instrumentation. Gate: the claimed result remains valid under documented release preconditions.
Mobile hypotheses#
| Hypothesis | Minimal controlled experiment | Evidence required before reporting |
|---|---|---|
| Exported Android provider exposes private data | Seed one record; request it from an unprivileged test app | Caller permissions, URI, synthetic record, expected denial |
| Deep link bypasses an account boundary | Open an owned-account resource link in a different owned session | Identity before/after, resulting unauthorized action/data |
| Web content reaches a privileged bridge | Load a harmless controlled page through a supported entry point | Origin/frame, exposed method, bounded protected operation |
| Logout leaves reusable authorization | Observe one owned token’s documented revocation lifecycle | Applicable policy, timing, separately scoped backend result |
| Local unlock is only a cosmetic gate | Trace whether protected data/key use requires authentication | Attacker prerequisite and actual protected data access |
| Imported file crosses a storage boundary | Import a synthetic file with a unique marker | Intended destination, actual destination, accessible privilege |
These are prioritization hypotheses, not claims that every listed behavior is a vulnerability. Screenshots, missing obfuscation, root detection, pinning, and generic API keys need program-specific impact and eligibility analysis.
Mobile checklist#
Android
- Package ID, publisher, store channel, version, hash, and signer recorded.
- Base and relevant split/dynamic-feature packages included.
- Device API, ABI, target SDK, and release/debug state recorded.
- Exported and dynamically registered components mapped.
- Component permissions and caller identity checked.
- Provider permissions, URI grants, and path handling reviewed.
- Intent forwarding and PendingIntent authority traced.
- Deep-link host, path, parameter, and session validation checked.
- WebView navigation, frames, and bridge methods mapped.
- Storage, logs, notifications, clipboard, and backups examined.
- Key storage, key use, and local authentication connected.
- Network trust behavior separated from lab CA changes.
- Account switching and logout tested with synthetic identities.
- Claimed ordinary-app issue reproduced without ADB-only privilege.
iOS
- Bundle ID, build, channel, hash, signing team, and device recorded.
- Simulator, device, and instrumented builds distinguished.
-
Info.plistand embedded extension metadata inspected. - Associated domains and custom URL schemes inventoried.
- URL parameters and account context validated independently.
- App Groups and Keychain access groups mapped.
- Keychain class, synchronization, and user-presence policy reviewed.
- Sensitive file protection evaluated for intended lock states.
- Extension and document-import trust boundaries exercised.
- WebView frames, navigation, and message handlers inspected.
- ATS configuration and actual certificate validation checked.
- Cache, logs, pasteboard, screenshots, and backup exposure assessed.
- Local authentication tied to sensitive data or key access.
- Re-signing/jailbreak limitations and release reproduction documented.
Mobile command cheat sheet#
Run these against owned lab devices and local artifacts. The commands inspect metadata or write analysis copies; they do not test discovered Internet hosts. Replace identifiers and paths deliberately. Android commands require the indicated SDK tools on PATH; JADX and Apktool require their supported Java runtime. ADB, APK Analyzer, APK signer, JADX CLI, Apktool CLI.
# Android: choose one owned device when more than one is attached.
adb devices -l
adb -s DEVICE_SERIAL shell pm path com.example.lab
adb -s DEVICE_SERIAL shell dumpsys package com.example.lab
apkanalyzer apk summary ./app.apk
apkanalyzer manifest print ./app.apk
apkanalyzer manifest permissions ./app.apk
apksigner verify --verbose --print-certs ./app.apk
jadx -d ./analysis-jadx ./app.apk
apktool d ./app.apk -o ./analysis-apktool
On macOS, inspect a locally acquired/extracted app without modifying its signature. codesign applies to both iOS bundles and macOS applications, but entitlements and runtime enforcement differ. Check installed man plutil / man codesign when tooling differs. OWASP Plutil, OWASP entitlement extraction.
plutil -p ./Payload/Lab.app/Info.plist
codesign -dv --verbose=4 ./Payload/Lab.app
codesign -d --entitlements :- ./Payload/Lab.app
Native sign-in and privacy coverage matrix#
Run identity tests through the shipped app and its authorized backend; a deep link opening a screen and a biometric prompt succeeding are only intermediate observations.
| Route | Controlled comparison | Required result |
|---|---|---|
| Native OAuth return | Normal callback versus another owned pending attempt | Code, verifier and transaction resolve to the correct final account |
| Account linking | Fresh link versus account-switch/cancelled flow | No external identity attaches to a different owned account |
| Passkey sign-in | Valid fixture versus wrong challenge/account fixture | The server rejects invalid assertions before session creation |
| Local protected key | Intended unlock versus supported alternate route | The protected operation observes the required local authentication state |
| Private UI/data | Public/private markers across lock and account switch | No new exposure to the stated unintended reader |
Use federation and token trust and authenticator lifecycle for the shared server-side procedures. Native-specific continuations are in Android components and iOS boundaries; use client storage and privacy for notifications, snapshots, clipboard and metadata-only hypotheses. RFC 8252 establishes the native external-agent/PKCE model, while Android distinguishes the client, credential provider and verifying server. Native OAuth, Credential Manager.
- Record the final authenticated account or actual key operation.
- Repeat with an untouched eligible build after tracing.
- Distinguish the OS authorization screen from backend validation.
Sources#
All sources accessed 2026-09-11. “Undated” means no reliable publication/update date was established from the retrieved page. Dates are page metadata, not a guarantee of completeness for every current OS version. Archived Apple sources are used for established mechanisms and command syntax, not blanket claims about current platform enforcement.
| ID | Publisher / exact source | Date | Use |
|---|---|---|---|
| MD01 | OWASP, OWASP MASVS | Undated; living standard | Mobile control organization |
| MD02 | OWASP, OWASP MASTG | Undated; living guide | Testing methodology and technique catalog |
| MD03 | OWASP, Mobile Application Security Weakness Enumeration | Undated; living catalog | Weakness taxonomy and version caution |
| MD04 | Google, Android Debug Bridge | Updated 2026-09-02 | Device/package inspection commands |
| MD05 | Google, apkanalyzer | Updated 2026-03-05 | APK metadata and manifest CLI |
| MD06 | Google, apksigner | Updated 2026-03-05 | APK certificate/signature inspection |
| MD07 | Google, android:exported | 2024-09-24, indexed page date | Component exposure and version dependence |
| MD08 | Google, Unsafe use of deep links | 2024-10-24, indexed page date | Deep-link validation |
| MD09 | Google, WebView – Native bridges | Updated 2024-10-15 | Frames, native bridges, origin boundaries |
| MD10 | Google, Network security configuration | Updated 2026-08-28 | Trust anchors and debug overrides |
| MD11 | Google, Content provider basics | Undated in this registry | Provider access model |
| MD12 | skylot/JADX maintainers, JADX repository and CLI | Undated; living README | Decompiler usage and limitations |
| MD13 | Apktool maintainers, CLI Parameters | Undated | Local package decoding |
| MD14 | OWASP, iOS Security Testing | Undated | iOS lab/platform distinctions |
| MD15 | Apple, Keychain data protection | Published 2024-12-19 | Access groups, classes, authentication constraints |
| MD16 | Apple, Support Universal Links | Updated 2016-12-15; archived | App/website association model |
| MD17 | OWASP, MASTG-TOOL-0062: Plutil | Undated | Property-list inspection tooling |
| MD18 | OWASP, MASTG-TECH-0111: Extracting Entitlements from MachO Binaries | Undated | Signing/entitlement inspection |
| MD19 | Apple, TN2206: macOS Code Signing In Depth | Page updated 2016-09-13; archived | Signature verification command and boundaries |
| MD20 | Microsoft / Mark Russinovich, Process Monitor | Published 2026-09-10 | Local process, registry, filesystem observation |
| MD21 | Microsoft, Get-AuthenticodeSignature | Undated; PowerShell 7.5 documentation | Windows signature inspection |
| MD22 | Microsoft, icacls | Updated 2025-06-09 | Windows ACL inspection |
| MD23 | OpenJS Foundation / Electron contributors, Security | Undated; living documentation | Renderer, navigation, IPC boundaries |
| MD24 | Electron contributors, Context Isolation | Undated | Bridge limitations and API exposure |
| MD25 | Google, Stay secure | Page states updated 2018-03-06 | Extension permissions and untrusted messages |
| MD26 | Google, Message passing | Page states updated 2025-12-03 | Extension messaging contexts |
| MD27 | Google, Native messaging | Page states updated 2023-02-27 | Native host manifest and execution boundary |
| MD28 | AFL++ maintainers, Fuzzing in Depth | Undated; living guide | Harness, corpus, execution, resource limits |
| MD29 | GNU Binutils / Sourceware, readelf | Undated | Non-executing ELF metadata inspection |