Supplied-session authenticated assessment
The non-default supplied-session-review feature adds one deliberately narrow
authenticated collection path to an explicit web-review assessment. It is a
development Preview, is not compiled into the curated release-bundle, and
does nothing unless the operator supplies a policy and its matching explicit
acquisition selection. V1–V6 use an out-of-band credential source; V7 instead
selects a private authorization-URI handoff and creates its own transaction.
The V1 policy accepts one complete Authorization header value. The V2 policy
accepts one small scanner-owned cookie jar from an explicit local file. The V3
policy accepts one username/password pair from an explicit local file and uses
it only for one bounded form-login transition: one anonymous GET of the exact
declared login page, followed by at most one explicit
application/x-www-form-urlencoded POST to that same URL. V3 does not discover
forms, guess credentials, retry an ambiguous submission, bypass MFA/CAPTCHA, or
perform OAuth. V1 and V2 still send no body or non-GET method. None of the
three modes loads a browser profile, automatically renews a session, or enables
an existing active vulnerability family under the selected credential.
Application-defined GET handling and the explicitly authorized V3 login
POST can have server-side effects, so the operator must authorize the whole
selected sequence. A principal alias is an operator assertion, not
authenticated identity.
V4 adds one separately selected scheduled cookie renewal, not login discovery
or an automatic retry after session loss. It starts with a supplied cookie jar,
reuses the existing --session-policy / --session-cookie-file command below,
and permits one bodyless same-application POST between healthy resource windows.
Only exact declared host-only, HttpOnly, nonpersistent Strict/Lax cookies are
accepted. The replacement advances the credential epoch immediately, then an
independent health GET must succeed before another resource is collected. A
failed renewal or new-epoch health check never restores or retries the old jar.
Policy V4: one scheduled renewal
Use the V2 policy fields and cookie TSV shape with these explicit differences:
schema = "security.supplied-session-policy/v4"
renewal_path = "/app/renew"
renewal_method = "post"
renewal_after_resource_count = 1
max_renewal_attempts = 1
# With two resources: startup + two resource/health pairs + renewal + health.
max_session_requests = 7
This is an excerpt, not a complete policy. Resources must number two or three;
the boundary must follow at least one and precede the final resource. The exact
request allowance is 3 + 2 * resource_count, within the unchanged nine-request
ceiling. There are at most five health checkpoints including renewal health.
All selected cookies must cover health, resources and the renewal path, remain
host-only and nonpersistent, and preserve the exact configured security
attributes. Domain, Max-Age/Expires, extensions, undeclared cookies, missing or
duplicated selected cookies fail closed at renewal. No browser cookie jar is used.
The explicitly authorized POST may have application-defined side effects.
security.supplied-session-audit/v4 records value-free renewal outcome, attempt,
bytes, applied-cookie count, epochs and the immediate health receipt. Epoch
identifies this run's credential context, not authenticated identity. Earlier
resources remain qualified only by their original same-epoch health evidence.
The new policy is initially rejected by the WordPress and WebSocket session
consumers before secret/output acquisition; v1–v3 behavior is unchanged. It
remains non-default, outside release-bundle and unpublished.
Policy V5: import one external public-code transaction
V5 is a deliberately narrower acquisition step, not an interactive OAuth/OIDC login client. The operator has already obtained an authorization code using a public client and S256. Termivar reads that explicitly supplied transaction once, redeems it at one policy-authorized token endpoint, then uses the existing Bearer startup/resource/checkpoint sequence. The IdP child has a distinct exact endpoint permit and connection pool, but shares the assessment's parent request, byte, cancellation and deadline accounting. A target permit does not authorize an IdP endpoint, and token claims cannot create either permit.
An illustrative operator invocation, after independently preparing the policy and private transaction file, is:
termivar scan https://app.example.test/app/ \
--profile web-review \
--session-policy ./oauth-session-policy.toml \
--session-oauth-code-file ./oauth-transaction.secret.tsv \
--report-dir ./assessment-oauth-session
This is not a claim that the example host was contacted. The actual process tests use owned numeric-loopback fixtures. The following complete non-secret policy illustrates the HTTPS profile:
schema = "security.supplied-session-policy/v5"
principal_alias = "operator-declared-reader"
credential_mechanism = "authorization_header"
credential_acquisition = "imported_oauth_code_pkce"
health_path = "/app/health"
health_json_field = "authenticated"
resources = ["/app/private/one", "/app/private/two"]
max_session_requests = 6
max_total_response_bytes = 65536
max_response_body_bytes = 8192
max_wall_time_ms = 10000
[oauth_code]
profile = "external-public-code-s256-bearer/v1"
transport_profile = "verified-https/v1"
issuer = "https://idp.example.test/realm"
token_endpoint = "https://idp.example.test/realm/token"
client_id = "explicit-public-client"
redirect_uri = "https://app.example.test/callback"
scopes = ["canary-read"]
max_token_response_bytes = 8192
Only a public-client authorization_code exchange is supported. There is no
client-secret, ID-token, refresh, device, implicit or password-grant path. The
issuer/token origin must match and the token endpoint must be under the declared
issuer path by segments; this is operator-supplied configuration, not discovery
or issuer authentication. The redirect URI is an equality binding, never a
callback that Termivar opens or follows. URLs are strict canonical, query-free,
fragment-free ASCII HTTPS references. The separately selected
owned-numeric-loopback-http/v1 profile permits only numeric-loopback HTTP test
origins, not hostname localhost, real cleartext credentials, or accept-any TLS.
The private UTF-8 TSV contains exactly eight unique rows, with these literal
field names: authorization_code, code_verifier, code_challenge, issuer,
client_id, redirect_uri, scope, received_unix_seconds. Values are not
trimmed; LF and CRLF delimit rows. Do not place the actual code, verifier, token,
or session cookie in a command, URL, issue or report. Use a fresh protected
regular file. The 16 KiB input bound, existing no-follow/regular-file acquisition
and trusted-parent/quiescent-file assumptions apply; this is not an immutable
filesystem snapshot or ancestor-containment guarantee.
The imported issuer/client/redirect/scope must equal the policy. The local S256 challenge must equal SHA-256 of the imported 43–128 byte verifier. Its asserted receipt time must be at most 60 seconds old, allowing at most five seconds of future skew; freshness is checked again immediately before dispatch. These are checks on supplied assertions: they do not prove a trusted clock, secure front-channel/state/nonce handling, that Termivar initiated authorization, or that the provider enforced PKCE. The code is attempted once per assessment, including after an ambiguous transport failure. Cross-run replay is left to the provider; no persistent secret-derived equality cache is created.
The body contains only grant_type, client_id, redirect_uri, code and
code_verifier. There is no cookie, Authorization header, ambient proxy,
redirect, retry, token endpoint guessing or implicit provider metadata fetch.
Complete status-200 uncoded JSON is required. The child token-body limit is at
most 32 KiB and must also fit the smaller common per-body and total limits.
Duplicate JSON keys, unsupported token types/ID tokens, malformed or truncated
responses cannot establish a credential. Bounded unknown extension values are
inert, not network or execution instructions.
This V1 acquisition profile deliberately requires an explicit exact scope
echo and an integer expires_in of 1–3,600 seconds. RFC 6749 allows the scope
field to be omitted when unchanged; such a response is unsupported by this
narrower profile, not necessarily an invalid OAuth server. openid and
offline_access scopes are unsupported. Only a Bearer access token fitting the
existing 8 KiB Authorization bound is admitted; a refresh token is discarded,
with its presence recorded only after successful parsing. Bearer syntax alone
does not prove that every possible hidden sender constraint is absent. The
token is opaque: it is not decoded, signature-verified, promoted to an identity,
or used to select a URL. Its declared lifetime narrows the use deadline; it is
not an observed server expiration decision.
For N resources, V5 requires the exact allowance 2 + 2 * N: one token POST,
one startup health GET, and a resource/health GET pair per resource. At most
three resources fit within the unchanged nine-request session ceiling (eight
requests at three resources). Smaller parent bounds win; the IdP has no fresh
allowance. Failed/partial transport bytes stay charged, including the existing
last-delivered-chunk overrun semantics. Request admission, completed response,
parsed credential and committed credential are distinct audit counters.
Only the private context-bound, read-back committed acquisition receipt can authorize subsequent Bearer work. Token acquisition alone does not establish target session health. The existing explicit JSON health predicate remains mandatory; a valid token for the wrong canary principal can still fail it. Cancellation, exhaustion, an unusable exchange or unhealthy startup never silently falls back to anonymous authenticated coverage. Ordinary anonymous assessment processing remains separately anonymous and unchanged.
security.supplied-session-audit/v5 saves value-free acquisition facts beside
the existing checkpoint/resource audit. It explicitly marks external context
as operator-declared and authorization initiation, browser callback, state/nonce,
OIDC identity and token-signature validation as not performed. Unevaluated scope,
refresh and lifetime facts are null rather than assertions of absence. It saves
neither raw transaction/token/response bytes nor their public unkeyed digests.
Guarded application buffers do not promise zero sensitive copies in HTTP/Python
libraries, the OS or allocator history. JSON retains the full audit, HTML and
Markdown explain its conditional meaning, and item-oriented CSV is not a full
acquisition transcript. Strict feature-independent readers preserve historical
v1–v4 documents; Compare treats IdP/policy/principal changes as context changes,
not vulnerability introduction or remediation. Verify checks bundle integrity
and the saved contract, not provider or identity truth.
V5 is initially refused by WordPress and WebSocket session consumers before secret acquisition; their earlier explicitly supported combinations do not change. It stays non-default, outside the seven-member curated bundle and published alpha.2. Full interactive authorization, OIDC, provider metadata/JWKS, renewal and external-service qualification remain separate work.
The owned Keycloak helper uses a pinned isolated provider and admin-prepared
SSO fixture, then a real public-code/S256/form-post authorization transaction.
It is not qualification of ordinary interactive login, MFA or consent. A separate
resource-client introspection oracle distinguishes the two canaries. Controller
and resource-server traffic is not charged or described as scanner traffic.
Only this test controller explicitly returns the provider's retained Secure
SSO cookies over HTTP to its exact owned numeric-loopback relay origin, port
and authorization path. Other destinations remain refused. This fixture-only
exception does not relax the product cookie policy or qualify browser behavior.
--validate-only checks fixture contracts and never establishes IdP execution:
python scripts/oauth_code_lab_acceptance.py --validate-only
python scripts/oauth_code_lab_acceptance.py \
--binary ./target/oauth-lab/release/termivar \
--output-dir ./new-private-safe-evidence \
--source-ref <exact-source-sha> --expect-version 0.10.0-alpha.3
The executable must already be built with supplied-session-review; actual lab
execution requires Linux Docker. Only its bounded safe JSON evidence is an
upload candidate, never temporary transaction files, provider logs or reports.
The current helper runs six imported-code scenarios and three scheduled-refresh
scenarios. It removes its owned fixture before all nine Verify/self-Compare
pairs and four controlled comparisons. The controller's old-refresh-token
refusal controls are separate from scanner traffic and do not upgrade the
product's server-replay assurance. Static tests and native synthetic CLI tests
are distinct from a completed real-provider lab.
Primary protocol references: RFC 6749, RFC 7636 and RFC 9700. These are development references, never scan-time fetch destinations.
Policy V6: one scheduled opaque-Bearer refresh
V6 uses the same explicit policy and imported transaction file as V5, but selects one additional scheduled token-endpoint exchange after the first resource and its healthy checkpoint. It is not automatic recovery after session loss, interactive authorization, OIDC, or a new credential authority. V5 still discards refresh material and never refreshes.
Use the complete V5 policy above with exactly these differences (this is an excerpt, not another complete policy):
schema = "security.supplied-session-policy/v6"
resources = ["/app/private/one", "/app/private/two"]
max_session_requests = 8
[oauth_refresh]
profile = "scheduled-public-bearer-refresh/v1"
after_resource_count = 1
max_attempts = 1
There must be exactly two selected resources and the exact eight-request child allowance. Existing smaller parent, body, byte and time bounds still win. The fixed successful schedule is:
| Order | Operation | Credential context |
|---|---|---|
| 1 | Imported-code token POST | IdP-only code/S256 form |
| 2 | Startup health GET | Access credential, epoch 1 |
| 3–4 | First resource GET and health GET | Epoch 1 |
| 5 | One refresh-token POST | IdP-only refresh form |
| 6 | Immediate renewal health GET | Replacement access credential, epoch 2 |
| 7–8 | Second resource GET and terminal health GET | Epoch 2 |
The two IdP phases share one private broker pool and the same parent accounting,
deadline and cancellation. Neither endpoint permit grants authority over the
other origin. The refresh form contains only grant_type, client_id,
refresh_token and the exact selected scope; it carries no target credential,
cookie, client secret or redirect URI. Refresh material is bounded printable
ASCII, is not trimmed, and uses form encoding rather than header syntax. The
form is limited independently to 16 KiB before allocation. The old refresh value
is discarded after the one attempt even on ambiguous failure. No retry, old-token
fallback or second refresh is permitted; the unused replacement refresh value
is also discarded.
Only the existing committed code receipt plus the exact committed first resource and healthy boundary can authorize this phase. A separate read-back committed renewal receipt is required for epoch 2; the old code receipt alone cannot qualify it. Renewed health must pass before another protected resource. Earlier resources retain their original acquisition epoch, including after later loss.
Both token responses require complete uncoded bounded JSON, an exact scope echo,
opaque Bearer material and a refresh value. V6 refuses id_token or top-level
cnf; unsupported hidden sender constraints remain unknown. The replacement
refresh value must differ locally. That inequality does not prove the server
revoked the old value, detects replay, authenticated an identity or implements
all OAuth security requirements. No token is decoded or signature-verified.
The provider-declared access lifetime may be replaced, but never extends or
resets the immutable assessment/policy wall deadline.
security.supplied-session-audit/v6 retains value-free acquisition, scheduled
renewal, epochs, counters and the separately labelled renewal health checkpoint.
Three common checkpoints plus that nested health are four actual GETs, not a
duplicate terminal checkpoint. Null unevaluated facts remain distinct from
negative observations. JSON/CSV preserve the full typed audit; HTML/Markdown
display both epochs and the conditional rotation meaning. Strict offline
readers retain historical v1–v5 contracts. Compare separates methodology,
declared context/epoch and coverage changes from vulnerability or remediation.
An illustrative command uses the same flags, with the V6 policy selected:
termivar scan https://app.example.test/app/ \
--profile web-review \
--session-policy ./oauth-renewal-policy.toml \
--session-oauth-code-file ./oauth-transaction.secret.tsv \
--report-dir ./assessment-oauth-renewal
The example host is not a test destination. Native process tests use owned numeric-loopback synthetic application/IdP responses; real-provider acceptance uses the separately pinned disposable Keycloak controller. Neither supplies ordinary browser login, consent, MFA or general production-IdP qualification. V6 WordPress/WebSocket composition is refused before secret/output acquisition. The feature remains non-default, outside the curated bundle and unpublished.
Build and invoke
Build an isolated candidate with the feature selected explicitly:
cargo build --locked -p termivar-cli \
--no-default-features --features supplied-session-review
For an authorized HTTPS application:
termivar scan https://app.example.test/app/ \
--profile web-review \
--session-policy ./session-policy.toml \
--session-auth-file ./authorization.secret \
--report-dir ./assessment-session
--session-auth-env and --session-auth-stdin are alternatives to the regular
file for a V1 policy. A V2 policy instead requires exactly one
--session-cookie-file:
termivar scan https://app.example.test/app/ \
--profile web-review \
--session-policy ./cookie-session-policy.toml \
--session-cookie-file ./cookies.secret.tsv \
--report-dir ./assessment-cookie-session
A V3 policy instead requires exactly one --session-login-file:
termivar scan https://app.example.test/app/ \
--profile web-review \
--session-policy ./form-login-policy.toml \
--session-login-file ./form-login.secret.tsv \
--report-dir ./assessment-form-login
The policy and credential acquisition mechanism must agree, and exactly one
matching credential source is required for V1–V6; V7 instead selects its private
authorization-URI handoff. Source mismatches and V4–V7 combined with
--wordpress-supplied-session are rejected after policy validation but before
output reservation or secret-file acquisition. The WordPress consumer accepts
only V1/V2/V3. There is no raw credential
argument, cookie environment source, browser import, or cookie stdin source.
HTTP is refused except for numeric-loopback development fixtures; Secure
cookies still require HTTPS, including on loopback.
For V1, the secret source contains the complete bounded header value, for
example a scheme and its credentials. Its bytes must not contain control
characters. The value is never a target, a scope grant, a principal identifier,
or a report field. V2 instead uses the ID-bound cookie rows described below.
V3's bounded TSV contains exactly one username<TAB>value and one
password<TAB>value row. It has no field-name freedom and neither value is
placed in argv, diagnostics, evidence or reports. The file is limited to 8 KiB,
the username to 256 UTF-8 bytes and the password to 4 KiB.
Policy V1
The strict bounded policy is TOML. This example selects two query-free, root-relative resources beneath the already selected application:
schema = "security.supplied-session-policy/v1"
principal_alias = "fixture-reader"
credential_mechanism = "authorization_header"
health_path = "/app/health"
health_json_field = "authenticated"
resources = ["/app/private", "/app/private-2"]
max_session_requests = 5
max_total_response_bytes = 131072
max_response_body_bytes = 32768
max_wall_time_ms = 5000
The selected target supplies the immutable scheme, host, effective port and
application directory. V1 accepts one to four resource paths. Paths and the
health endpoint must remain inside that application, be query- and
fragment-free, and pass the raw-reference safety checks. Login, logout, action,
download and administration-style paths are rejected by V1. This bounded
denylist narrows accidental selection; it does not prove that an admitted GET
is side-effect-free. A discovered link cannot extend the policy.
The policy may allow at most nine supplied-session requests. It must budget one startup health check plus one resource request and one following health check for every selected resource. Per-body, total-response and wall-time limits are narrowing ceilings; the assessment's smaller parent limits, cancellation and deadline always win.
The health response must be a complete successful JSON representation whose
named top-level field is exactly the Boolean true. A 200 login page, a
truthy string, missing field, duplicate JSON key, truncated body, redirect or
transport error does not establish healthy session context.
Policy V2 and supplied cookie file
V2 keeps the same application, resource, health and request limits, and adds strict non-secret metadata for one to eight cookies. This HTTPS example declares two same-name cookies with different paths:
schema = "security.supplied-session-policy/v2"
principal_alias = "fixture-reader"
credential_mechanism = "cookie_jar"
cookie_update_policy = "stop_on_selected_cookie"
health_path = "/app/health"
health_json_field = "authenticated"
resources = ["/app/private", "/app/private-2"]
max_session_requests = 5
max_total_response_bytes = 131072
max_response_body_bytes = 32768
max_wall_time_ms = 5000
[[cookies]]
id = "broad-session"
name = "session"
domain = "app.example.test"
host_only = true
path = "/app/"
secure = true
http_only = true
same_site = "lax"
[[cookies]]
id = "narrow-session"
name = "session"
domain = "app.example.test"
host_only = true
path = "/app/private"
secure = true
http_only = true
same_site = "strict"
expires_unix_seconds = 4102444800
The secret file contains exactly one id<TAB>value row for every declared
cookie. IDs bind values to reviewed metadata and are not sent; values are
composed into Cookie only for an exact admitted request. The file is limited
to 16 KiB, each value to 4 KiB, and the composed header to 8 KiB. Duplicate,
unknown, missing, expired, malformed, or initially non-covering rows fail before
network execution. Applicable same-name cookies are emitted in longer-path-first
order. If any selected persistent cookie expires during execution, the fixed
epoch fails closed before another credentialed request; the runtime does not
silently continue with a smaller cookie set.
V2 validates host-only/domain, path, Secure, expiry, HttpOnly and SameSite
metadata. Protocol applicability is intersected with the policy's narrower exact
application authority; a Domain declaration never grants another target host.
HttpOnly and SameSite are preserved facts, not a claim that this non-browser
client reproduces browser script or CSRF behavior.
Policy V3 and bounded form login
V3 keeps the same exact application, health predicate, response-byte and
wall-time authority, but establishes the cookie jar through one explicit login
transition. This two-resource example uses seven requests: login-page GET,
login POST, startup health, and one resource/health pair per resource.
schema = "security.supplied-session-policy/v3"
principal_alias = "fixture-reader"
credential_mechanism = "cookie_jar"
credential_acquisition = "bounded_form_login"
cookie_update_policy = "stop_on_selected_cookie"
login_path = "/app/login"
login_method = "post"
login_encoding = "application/x-www-form-urlencoded"
username_field = "username"
password_field = "password"
csrf_field = "csrf_token"
max_login_attempts = 1
health_path = "/app/health"
health_json_field = "authenticated"
resources = ["/app/private", "/app/private-2"]
max_session_requests = 7
max_total_response_bytes = 131072
max_response_body_bytes = 32768
max_wall_time_ms = 5000
[[cookies]]
id = "generated-session"
name = "session"
domain = "app.example.test"
host_only = true
path = "/app/"
secure = true
http_only = true
same_site = "lax"
V3 admits one to three resources. Its exact request count is
3 + (2 * resource_count) and remains within the unchanged nine-request hard
ceiling. It begins without a cookie. The login page is one anonymous, complete,
untruncated status-200 UTF-8 text/html representation of at most 64 KiB and
2,048 DOM nodes. Exactly one eligible form must have no action or an action
exactly equal to the configured login path, an explicit POST method, and no
enctype or exactly application/x-www-form-urlencoded. It must contain
exactly the configured enabled username input (default/text), password input
(type=password) and hidden CSRF input. The CSRF value is required, bounded to
2,048 UTF-8 bytes, guarded in memory and never serialized. Missing, duplicate,
disabled, wrong-type, malformed, partial or over-limit input authorizes no
submission. Returning this operator-selected field is login plumbing, not a
test or claim that the application resists CSRF.
The form body contains only the three configured fields and values. The same exact login URL receives at most one POST. Redirect following and retry are disabled; a timeout or other ambiguous outcome is not resubmitted. There is no pre-session cookie, Authorization value, ambient cookie store, credential guessing, login-path discovery or fallback form selection.
Only response cookies matching the policy's declared host-only application
cookie metadata may establish the scanner-owned jar. V3 accepts session cookies
only: the exact application host, an applicable safe path, Secure matching
the selected scheme, HttpOnly, SameSite=Strict|Lax, and no expiry. The
operator's application scope remains narrower than cookie protocol scope. A
cookie response, successful status or familiar page text does not establish
login success. After the form transition, the existing complete JSON startup
health predicate is the sole success oracle. Only a valid declared-cookie
transition followed by healthy startup advances the credential lifecycle from
epoch zero to epoch one and allows resource collection. Acquiring a cookie alone
does not advance the epoch or establish the expected principal.
Execution and evidence
The runtime creates a context-isolated, redirect-disabled, no-proxy child of the existing assessment broker. It sends only the configured V1 authorization value, request-applicable V2 cookie pairs, or the V3 jar established by the single bounded login transition to the exact selected application and never forwards them across a redirect. It does not enable reqwest's automatic cookie store. V1/V2 start with the supplied credential at epoch one. V3 records its anonymous login-page GET and explicit login POST separately, starts at epoch zero without a cookie, and reaches epoch one only after a valid cookie transition and the independent startup JSON health check. A cookie acquired for the wrong principal therefore retains epoch zero when startup is unhealthy and qualifies no resource. Each dispatched resource is followed by a deterministic health checkpoint before it can become committed authenticated coverage; loss stops later supplied-session work. There is no silent anonymous retry, POST retry or automatic refresh.
Every V2 response and every V3 response after initial session establishment is
checked for bounded Set-Cookie fields before its body can be committed. An
update to a selected cookie stops later supplied-session work with
credential_update_required; malformed, over-limit or otherwise unusable update
metadata stops with credential_update_unusable. An unselected response cookie
is counted but not applied. Outside the initial V3 login response, response
updates remain unapplied and automatic renewal remains absent. V1/V2 keep epoch
one; successful V3 establishment changes only epoch zero to epoch one.
Each complete 200 resource response remains a body-free staged observation
until its immediately following health checkpoint is both healthy and committed.
Only then does it count as authenticated committed coverage. If that checkpoint
is unhealthy or unavailable, the response remains health_unqualified, counts
as dispatched activity, and does not count as authenticated coverage.
The central report adds security.supplied-session-audit/v1 for V1,
security.supplied-session-audit/v2 for V2, or
security.supplied-session-audit/v3 for V3. It records opaque
policy, application, principal and resource references; the operator-declared
assurance; checkpoint and resource outcomes; and reconciled request/byte counts.
V2 additionally records only non-secret cookie-policy aggregates and value-free
selected/unselected/unusable update counts; it never records cookie names,
values, domains, paths, source file locations or response fields.
V3 additionally records a login reference, bounded page/CSRF/submission/cookie
outcomes, attempt and acquired-cookie counts, response-byte accounting, the
zero-to-one epoch transition when established, and that no pre-session cookie
was applied. It never records the username, password, CSRF value, form body,
cookie values, field names, raw login URL or secret-file path.
The configured response-byte limit and an exact exceeded flag are separate from
the unclamped transport count because the broker charges a final delivered chunk
even when only the bounded prefix is retained.
It never records the Authorization value, response body, readable resource URL,
or an unkeyed credential digest. Evidence committed before a later loss remains
qualified by its recorded checkpoints. Startup and boundary checks do not prove
continuous authentication between them.
This slice produces an audit, not a vulnerability item. Exploit execution and
impact validation remain not_performed. An empty or interrupted authenticated
coverage window is not proof that the application denied access or is secure.
Offline Verify checks bundle integrity; Compare describes audit/context and
coverage changes without treating logout or a different principal as
remediation. A changed credential-acquisition mode or login lifecycle is likewise
a context/methodology change, not proof that target access was added, removed or
fixed.
Explicit WordPress consumer
When the executable is compiled with both supplied-session-review and
wordpress-review, an operator can additionally select:
--profile web-review --wordpress-review --wordpress-discovery \
--wordpress-supplied-session
One V1, V2 or V3 supplied-session policy and exactly one matching credential
source remain mandatory. V3 uses the existing --session-login-file, not an
imported cookie file. V4–V7 remain refused before secret acquisition. The
composition interprets only complete selected resource
HTML whose immediately following health checkpoint and S01 resource evidence
were committed. It binds the observation to the policy, application, principal,
credential mechanism and actual final session epoch. A login response,
incompatible application, unsupported representation, truncated body, health loss or uncommitted evidence
cannot nominate WordPress metadata.
The discovery schema remains security.wordpress-discovery-audit/v4 with the
same field shape. Historical V1/V2 documents retain collection policy
termivar.wordpress-supplied-session-metadata-discovery/v1, mode
committed_supplied_session_resources and epoch one. V3 instead selects policy
termivar.wordpress-supplied-session-metadata-discovery/v2 and mode
form_login_supplied_session_resources. Its nested session_epoch is the
actual final epoch: zero unless acquisition and startup health established the
session, otherwise one. A received cookie followed by unhealthy startup still
has final epoch zero. Epoch
zero permits only empty, not_established / not_evaluated planned page rows,
zero dispatched resources, zero committed/interpreted activity, and no
supporting component evidence.
The S01 planned resource rows still use epoch one; they are not rewritten to
zero. Wrong-principal startup failure retains epoch zero; health loss after
successful startup retains epoch one. Neither invents committed WordPress pages. Strict writers and offline
readers cross-link the new mode, policy and actual lifecycle while preserving
the historical V1/V2 meanings.
Eligible component hints may nominate only the existing public WordPress
metadata requests. Those requests remain anonymous, same-origin and broker-owned
inside the existing shared WordPress allowance; no Cookie or Authorization value
is forwarded. Authenticated-page JS/CSS does not nominate fingerprint acquisition
in this initial integration. The authenticated page ledger is separate from the
anonymous observed-page collection, and a different principal or epoch is a
context change rather than evidence of installation change or remediation.
Private resource URLs do not become ordinary crawl targets. There are no new
flags, features or request allowances. A task-owned V3-shaped WordPress login
route is not native wp-login.php compatibility; see the
owned-fixture invocation.
Explicit WebSocket consumer
When the executable is compiled with both supplied-session-review and
websocket-review, the V1 Authorization-header session may be consumed by the
bounded WebSocket review only when the operator additionally selects
--websocket-supplied-session. The combined invocation requires exactly one
selected protected resource:
termivar scan http://127.0.0.1:<owned-fixture-port>/app/ \
--profile web-review \
--session-policy session-policy.toml \
--session-auth-file authorization.secret \
--websocket-review-policy websocket-policy.json \
--websocket-supplied-session \
--report-dir assessment
The fixed schedule is startup health, protected-resource staging, one credentialed WebSocket handshake and bounded exchange, then terminal health. Both health checkpoints are required before either the staged resource or the WebSocket result is qualified for the declared application, principal and epoch. Startup loss prevents the WebSocket dispatch. Terminal loss preserves only the value-free wire result and leaves the context unqualified; it does not publish the protected resource as committed evidence.
V2 cookie and V3 form-login policies are intentionally unsupported by this consumer and are rejected before credential or output acquisition. The same session cannot be selected simultaneously by the WordPress and WebSocket consumers. Without the explicit integration flag, WebSocket review remains its existing anonymous isolated connection and receives neither the Authorization value nor supplied-session context. The WebSocket connection shares the parent deadline and accounting; it does not create a fresh session-request allowance. Health checkpoints qualify only the bounded declared context window and do not establish continuous authentication.
Policy V7: client-initiated public-client authorization
V7 generates independent 32-byte OS-random PKCE verifier, state and opaque
transaction reference. It supports only S256, the explicitly configured issuer
and authorization/token endpoints, and one previously registered IPv4 callback
http://127.0.0.1:<fixed-unprivileged-port>/termivar/oauth/callback. It does not
register a client, open a browser, discover endpoints or read a browser profile.
The operator completes any login, MFA and consent in their own user agent.
No OIDC nonce, identity or token-signature validation is performed.
With an owned application and an already configured public client, the CLI is:
termivar scan https://application.example/app/ --profile web-review \
--session-policy initiated-policy.toml \
--session-oauth-authorization-output private-authorization.txt \
--report-dir assessment
The policy is a complete strict V7 document, not an imported code transaction:
schema = "security.supplied-session-policy/v7"
principal_alias = "operator-declared-user"
credential_mechanism = "authorization_header"
credential_acquisition = "client_initiated_oauth_pkce"
health_path = "/app/health"
health_json_field = "authenticated"
resources = ["/app/read-only-one", "/app/read-only-two"]
max_session_requests = 6
max_total_response_bytes = 65536
max_response_body_bytes = 8192
max_wall_time_ms = 10000
[oauth_code]
profile = "client-initiated-public-code-s256-bearer/v1"
transport_profile = "verified-https/v1"
issuer = "https://identity.example/issuer"
token_endpoint = "https://identity.example/issuer/token"
client_id = "previously-registered-public-client"
redirect_uri = "http://127.0.0.1:43123/termivar/oauth/callback"
scopes = ["read-only"]
max_token_response_bytes = 8192
[oauth_authorization]
profile = "loopback-form-post-state/v1"
authorization_endpoint = "https://identity.example/issuer/authorize"
max_wait_ms = 30000
This example is not a supplied endpoint permit or a claim that an external
provider was tested. The health endpoint must implement the operator's protected
JSON predicate; status 200 alone does not qualify it. Real issuer/token/target
connections require normal verified HTTPS. The cleartext exception above is
only the numeric-loopback response receiver. The separately selected
owned-numeric-loopback-http/v1 issuer profile remains an owned-test exception,
not RFC 9207 HTTPS conformance. openid is not supported by this token profile.
The output is create-only, outside the report bundle, in a trusted private local directory. It contains one authorization URI plus a newline; it contains state and challenge, never the verifier. The file is usable only while the command waits. Unix creation is mode 0600; Windows inherits the directory ACL. No claim is made that ancestor checks form an atomic snapshot or that an arbitrary host library writer is interruptible. Only the still-owned file is removed; a replacement or unverifiable residual is retained with a static warning.
The listener is bound before handoff, receives at most one connection and closes
before token acquisition/report composition. Only an exact Host/path, bounded
complete application/x-www-form-urlencoded POST can qualify. A present Origin
must match; a missing Origin is not itself a protocol error. Duplicate known
form parameters, wrong/missing state or issuer, ambiguous framing, credential
headers, oversized/partial input and stale responses fail closed without another
accept or token retry. Unknown bounded form extensions are ignored and counted.
Provider errors retain only a closed class, never descriptions or redirects.
There is one parent runtime and immutable deadline. Authorization wait is at
most 30 seconds under that parent; after a qualified callback the existing
session window remains at most ten seconds and the smaller remaining parent
time wins. One callback reception reservation consumes a parent logical attempt,
even if no connection arrives. Raw received framing/form bytes are charged to
parent response accounting, not outgoing token request-body accounting. A fixed
inert reply has a separate actual-write counter, not a peer-receipt assertion.
The session's 2 + 2 * resource_count outgoing requests remain separately
reported; at most three resources plus the callback reservation fit the existing
nine-admission ceiling. No WordPress/WebSocket composition or refresh is selected
by V7. Generic anonymous subject processing is not suppressed.
Only exact committed callback evidence authorizes the existing once-only IdP token POST. Every subsequent health/resource credential use revalidates the callback → token evidence chain in the same knowledge authority. Saved reports and imported V5/V6 TSV files cannot mint that authority. Matching state/issuer and a redeemed opaque Bearer credential do not authenticate a browser, principal, token signature or remote PKCE enforcement. Session health still qualifies only its checkpoints and bounded coverage window.
V7 saves separate oauth_authorization and oauth_token_exchange audit sections;
it does not relabel the historical external-transaction oauth_code audit.
Codes, verifier, state, raw forms, URI/query and local paths are not report data.
Verify remains an integrity check. Compare distinguishes policy/methodology,
coverage and accounting; per-run proof references and elapsed time do not become
stable principal identity or target-change evidence.
Secret boundary
Policy validation happens before the selected secret source is read. File input uses the shared regular-file, final-component no-follow and bounded-read contract; trusted parents and mutable-file limitations still apply. Owned CLI intake bytes are guarded through constructor handoff, but this is not a claim that operating-system environment storage, allocator history, process dumps, HTTP-library buffers or every downstream copy is erased. See Credential-input guarantees and limits.
Protocol references
The boundaries follow the current OWASP Authentication Cheat Sheet, OWASP CSRF Prevention Cheat Sheet, and OWASP Session Management Cheat Sheet for TLS-protected login, an explicit anti-CSRF input and scoped session handling, and RFC 9110 for HTTP semantics. V2 applies the domain/path/Secure and ordering rules used from RFC 6265 only inside the operator's narrower application authority; V3 narrows this further to declared host-only session cookies. These references define protocol expectations, not proof that an observed principal or session is authentic.
V7's public-client transaction uses RFC 7636 S256, RFC 9207 issuer comparison and the OAuth form-post response mode. Its fixed numeric-loopback callback is a deliberately narrower profile than general RFC 8252 native-client support; it supports neither dynamic redirect rewriting nor a browser launch mechanism. The OAuth security BCP informs these boundaries without establishing general provider or OIDC compatibility.