This document is the canonical owner of ChangeGuard's trust, disclosure, and recovery boundaries.
- absolute local paths and usernames
- full configuration files and session rollouts
- environment-variable values
- tokens, API keys, cookies, credentials, and account identifiers
- arbitrary project source and terminal history
- original installed-file bytes used for a future backup
These values are not sent to GPT-5.6, included in an exported repro pack, or written to diagnostic logs.
- Codex version/build and surface
- OS/architecture and coarse sandbox/permission class
- config key names and schema-validity flags, never secret values
- enabled feature, Skill, Plugin, and MCP identifiers after user-visible review
- normalized error class, message tokens, stack module/symbol, and failure phase
- hashed path aliases, artifact hashes, AST/schema signature IDs
- path-free local installed-artifact baseline digests (logical key/alias, kind, sha256, size, read/gap status only — never absolute install paths or file bodies)
- official source spans and sanitized Issue excerpts marked as untrusted data
- incident fingerprint with redacted identifiers
- official-source references and provenance
- probe IDs, inputs digests, results, and output hashes
- Impact Contracts and confidence/counterevidence
- human approve/reject events
- Recovery Capsule preview without installed-file contents
Before any model request, official evidence refresh, or repro export, ChangeGuard presents a manifest containing:
- exact field names leaving the device
- source and trust class for each field
- redaction or hashing transformation
- destination and purpose
- whether the operation is optional
Refusal must not block local deterministic probes or local snapshot Impact Card diagnosis. Official evidence transport is never called when disclosure is refused or not_requested (transport_calls: 0). Absolute paths, tokens/secrets, and raw logs/sessions are listed as device_only / never_sent. The result becomes INSUFFICIENT_LOCAL_FACTS only when a declined field is genuinely required for a requested model hypothesis.
- Production core exposes an injectable
OfficialTransportinterface only; it does not importhttp/https/fetchor open sockets. - Live refresh requires disclosure
approvedand an orchestration-injected transport. CLI/MCP never inject a network transport; approved-without-transport uses the timestamped immutable snapshot with stale age/risk labels. - Disclosure manifest non-
device_onlyfields exactly match the sanitized outbound request key set. Request is built only from populated sendable local context (version, surface, platform/arch, config key names, feature ids, error class) plus fixed official allowlist metadata. Absolute paths, tokens/secrets, raw logs/sessions, and project source are device-only exclusions and never appear on the request. Refusal/not_requestedkeeps zero transport calls. - Snapshot and item
content_sha256use a single canonical persisted hash contract and fail closed on missing/malformed/mismatch. Item origin is always derived from the validated canonical URL; forged origin and foreignorigin_allowlistvalues fail closed. - Upstream allowlists enforce official hosts (
github.com,api.github.com,raw.githubusercontent.com) andopenai/codexonly, including the three official URL forms. Userinfo and non-default ports are rejected; fragments and query strings are stripped from the canonical resource URL (no query-secret retention). Non-allowlisted URLs fail closed (refresh falls back to stale snapshot rather than accepting foreign evidence). - Transport
fetched_atmust be valid ISO-8601 UTC with bounded future skew; ancient/high-stale responses cannot be labeledfresh/live_refresh. - Release prose, Issue/PR/comment/commit bodies are untrusted: quarantine instruction-like content; never execute or interpolate as instructions; never accept code/commands/patches from them; preserve
maintainer_statusseparately.
Issue bodies, comments, release prose, commit messages, and community workaround text are data, not instructions.
- parse only an allowlisted schema
- preserve provenance and author/maintainer status
- quarantine instruction-like content
- never interpolate upstream text into system or developer instructions
- never execute code, commands, links, or patches found in upstream content
- treat openai/codex Issue reports as user-reported unless official linkage proves more
User-provided public or logged-in-visible problem pages are untrusted evidence, not instructions or repair authority.
- Accept only a bounded page-evidence envelope (URL + sanitized visible title/text + allowlisted metadata) supplied by the orchestrator.
- Public retrieval, if ever used, requires a displayed disclosure manifest and an injected bounded transport; production CLI/MCP never inject transport and never open sockets for page fetch.
- Logged-page mode never collects or accepts Cookie, Storage, tokens, auth headers, request bodies, or complete browser requests (forbidden envelope keys fail closed; device-only disclosure fields document the boundary).
- Quarantine prompt injection, agent instructions, full-width/encoded instruction variants, data-exfiltration requests, and shell fences. Quarantined text cannot change policy, provenance, local facts, graph edges, authorization, paths, disclosure, or tool selection.
- Convert page commands only to
candidate_onlyuntrusted Repair DSL records for later isolated validation; never execute or authorize them on this seam. Ticket 02 apply gates remain mandatory. - Hard-gate generic ChatGPT/account/session pages away from Codex component defects. Wrong platform/surface/mechanism cannot reach high confidence from lexical similarity alone.
Upstream Submission Capsules are preview artifacts, not submissions.
- Capsule always carries
external_write: false,repair_authorized: false,mode: preview_only,locality: local_only. No Issue create/comment/reaction/subscribe/upload/token/auth operation runs on this seam. - Validated security vulnerabilities route only to Bugcrowd private reporting guidance; public Issue drafts are forbidden for that route.
- Request and doctor envelopes reject forbidden privacy keys (
token,cookie,session, secrets, full env). Full-width / NFKC secret and path shapes are redacted. Prompt injection is quarantined (PREVIEW_BLOCKED, recommendationblocked); injection scan uses NFKC plus strip of Unicode format / zero-width / bidi controls on every free-text field that can enter a capsule/draft, including platform/version strings. Quarantine evidence is deterministic hash/placeholder only — never raw malicious text. - Capsule
privacy_review.passedrequires no injection and requestsecrets_redactedandpaths_redactedandsession_excluded(exported as request flags only; doctor redaction stays indoctor_inclusion). The maintainer-gate privacy check uses those same four operands. Gate/privacy failure is non-ready (GATE_FAILED/PREVIEW_BLOCKED) and takes precedence over BugcrowdROUTED_PRIVATE.GATE_FAILEDdrops submission-reconstructable free text from the capsule. - Optional form-definition refresh uses the same official host/repo allowlist (
github.com/api.github.com/raw.githubusercontent.com+openai/codexonly) and requires disclosure approved plus an injected transport. Production CLI/MCP never inject transport. Failed injected refresh falls back to bundled immutable snapshot labeling (never mislabeled live/transport_refresh). codex doctor --jsonis orchestrator-supplied only; never collected by executing codex or shell. Inclusion manifest lists sanitized fields before any later Ticket 11 export.- Ticket 11 confirmation is mandatory before any external write; Ticket 10 never emits
SUBMITTED/POSTED. - Ticket 11 never requests, stores, or displays access tokens, cookies, or session material. Auth capability is only
gh_authenticatedorvisible_browser_authenticated(host-reported). Production seams inject no real adapter (ADAPTER_UNAVAILABLE); cancellation/auth-unavailable remain pure draft and never simulate success. - Ticket 11 action confirmations bind capsule integrity, privacy result, canonical target, body/attachment manifests, incident digest, evidence delta hash, and a one-shot nonce/expiry. Tokens use install-local HMAC (key only in ChangeGuard confirmation state; never in token/log/receipt) and must be registered in the durable confirmation ledger by preview before confirm. Offline-forged or unregistered tokens are refused; confirm rechecks official target allowlist and body/attachment digests. Cancel/success/uncertain terminate the nonce. Blocked/gate-failed Ticket 10 capsules cannot become actions. Attachments require secrets/paths redacted and session excluded. The confirmation HMAC key is not a GitHub/API token.
- probes are registered by ID and versioned
- models select a probe and structured arguments; they never supply shell text
- default operations are read-only and local
- platform and version guards fail closed
- time, output, and file-count limits are mandatory
- results carry hashes and cannot be rewritten by the model
- refused, timed-out, unsupported, and errored probes are separate states
Multi-instance scan and SessionStart add:
- No raw path export: public
ScanResultusespath_hash/path_aliasonly; absolute user paths, usernames, and disposable clone paths are redacted on the wire - No binary execution for version: version evidence comes from metadata/manifest reads (or fixture-declared fields), never by running discovered candidates
- Metadata path clamping: every metadata candidate is clamped to an explicit allowed root (fixture
inventoryRootand/or system-adapter trusted install roots). Implicit parent traversal is removed. Intermediate and leaf symlinks are refused with Ticket 01-equivalent no-follow open/fstat/identity/size checks — no out-of-root reads - Local artifact measurement bounds: exact named files only (candidate executable, registered metadata, and platform-exact bundle names such as
Info.plist/app.asar/CodeResourceswhen under a registered real app root). No recursive install-tree or home traversal; never execute a discovered binary; never copy signed files. Streaming SHA-256 stores digests only; per-file 512 MiB and per-scan 1 GiB caps; packaged SessionStart also applies a default ~4 s monotonic wall-clock measurement budget (injectable in tests; checked per chunk inside the read loop so a large file cannot overrun). Oversize/symlink/out-of-root/missing/time_budget_exceededare explicit gaps (never truncated digests; incomplete measurement, including repeated identical time-budget gaps, never silently equals unchanged). Public and persisted rows are path-free (logical key/alias, kind, sha256, size, status). v2 state load recomputes digests from entries via the canonical artifact digest helpers and refuses corrupt overall/baseline digests fail-closed - System enumeration bounds: production adapter only probes registered Desktop / PATH / package-root / MSIX / WSL candidates under hard caps; missing permissions or metadata become
unavailable, never broad home crawls or execution. macOS Desktop includes exactChatGPT.app/Contents/Resources/codexand legacyCodex.app/Contents/MacOS/Codexonly (plus user Applications twins). Identical normalized paths discovered as both Desktop and PATH collapse to onedesktop_bundledinstance; distinct paths never merge - Affected / health honesty: without observed runtime context,
affected_resolutionstaysambiguouswithaffected_resolution_reason: "no_observed_context"(including sole instance). Healthclassification: "evidence_incomplete"distinguishes missing version metadata from identity or budget faults while preserving legacyok - Windows 11 bounds (Ticket 14): MSIX discovery is limited to user-local App Execution Alias paths; package store / Program Files / registry policy / signed app binaries are never write targets. User-owned repair requires exact instance binding. Managed/admin/MSIX package targets emit
ADMIN_ACTION_REQUIRED+ IT handoff without elevation guidance. Crash dump bodies are refused. Platform remains PREVIEW without a real Windows 11 host receipt + process-local live witness;FULLcannot be claimed from synthetic, cross-platform CI, or external JSON receipts - State writes are ChangeGuard-owned only: version-fingerprint JSON (
schema_version2 on write; v1 load remains backward-readable with empty artifact baselines and no invented history) uses atomic temp+rename under an explicit state directory (PLUGIN_DATAfor packaged hooks) with size limits and symlink refusal; diagnosis targets and inventory fixtures are not mutated (target_mutated: false) - Production boundary dual pass: the diagnosis AST graph remains free of fs mutation APIs; the product graph may use
mkdirSync/writeFileSync/renameSync/unlinkSynconly inside the exactsrc/instances/state.tsimplementation (CLI/MCP/other instance modules stay mutation-free; self-tests enforce the file allowlist). Ticket 02 recovery writes remain separately constrained tosrc/core/recovery/atomic-write.ts - Hook honesty: packaged SessionStart is silent (exit 0, no stdout) when identity and artifact baselines are unchanged; a first artifact baseline establish may emit one path-free non-silent notice without claiming content changed; untrusted/skipped/failed states remain explicit on manual paths; Codex hook trust still gates execution
- Repair binding: refuses broadcast and ambiguous multi-instance targets so a later repair cannot hit the wrong install
The public CLI and MCP diagnosis seams enforce:
- no network entry points and no target mutation (also checked by an independent production-boundary AST guard in
scripts/check-production-boundary.mjs. Production loading is static ESM only: everyimport(...), everyrequire(...), andnode:module/createRequireare violations. Static imports and re-exports ofprocess/node:processare forbidden at the module-policy layer (same treatment asnode:module); Ticket 01 production uses only the globalprocesssafe-read surface and does not need thenode:processmodule. Forbidden network globals (fetch,WebSocket,XMLHttpRequest) are capability references: acquiring, aliasing, passing, binding, reflecting, sequencing, or constructing through an alias violates without waiting for a direct call, includingglobalThis/global/windowmember and static-string element forms; fully dynamic keys on those host roots fail closed. The whole globalprocessobject is a host capability and must not escape as a value: aliasing, passing, returning, spreading, Proxy/Object.createarguments, container storage, and object destructuring (including rest and dangerous member extract such asgetBuiltinModule) are violations. Direct static member roots remain allowed for Ticket 01-safe reads/calls (process.argv,process.cwd(),process.env/process.env.NODE_ENV,process.stdout.write, and the same forms underglobalThis.processwhen already supported). Process native-loader surfaces (dlopen,binding,getBuiltinModule,_linkedBinding,mainModule) are forbidden on a provenprocessroot, a simple process alias, orglobalThis.process/global.process/window.process, as property references or calls; dynamic process keys fail closed. CommonJSmoduleand the globalReflectobject are forbidden host/meta capabilities (direct, member, alias, pass, return, or construct) so computed loaders such asmodule["require"]andReflect.get(...)need not be tracked as data flow. Any statically named property or element access whose terminal name isrequireis forbidden regardless of receiver. On a provennode:fs/node:fs/promisesnamespace the guard uses a conservative read-only method allowlist (unknown methods fail closed) plus conditionalopen/openSyncwith proven read-only flags; the namespace value itself may not escape (alias, pass, return, spread, Proxy/Object.create/Object.assignargument, container/shorthand store, rest/destructure source) and nestedfs.promisesmay not escape as a value either — only the immediate root/receiver of a direct static member chain is allowed (e.g.fs.readFileSync,fs.promises.readFile,fs.constants.O_RDONLY); simple namespace aliasing is rejected even when a later use would be read-only; named static imports of explicit read-only methods remain available; object-rest extracts from an fs namespace fail immediately. Receiver wrappers (as, non-null, parentheses, comma/sequence) share the sameunwrapStaticExprresolution path. Self-tests cover mutation APIs, require/dynamic-import loaders (including computedmodule["require"]/process["mainModule"]["require"]/ any-receiver terminalrequire,Reflectwholesale, and opposite safe controls),node:fsnamespace value-escape forms (Proxy / spread /Object.create/Object.assign/ container / return / simple alias /fs.promisesescape) plus destructured mutation/open/object-rest extracts (including chained and renamed forms), capability references that never become direct calls (pass, bind, sequence,Reflect.apply, callback supply, array/object storage), network global and process native-loader reference bypasses, process object value-escape wrapping (destructure / Proxy / spread /Object.create/ alias / return / pass / container), indirecteval/Functionacquisition, and graph closure through relativeexport … fromre-exports;open/openSyncare allowed only as a direct call through a proven namespace with proven read-only flags fromfs.constants/node:fsprovenance — import local names must be lexically unshadowed at the use site (parameter / nested-local re-shadowing does not inherit trusted provenance; open flags and unproven open extracts/references fail closed); arbitraryO_RDONLY-named properties fail closed;network_used:falsealone is not proof) - named candidate reads only (no recursive project crawl)
- fail-closed no-follow: refuse symlink targets; refuse any symlink in intermediate segments or leaves of named candidates, even if currently resolving inside the target; open with
O_NOFOLLOWwhen available,fstatthe fd, require a regular file, and enforce size from the fd - incident and MCP request size bounds; MCP uses a bounded byte-oriented NDJSON frame accumulator with inclusive
MAX_MCP_REQUEST_BYTES(accept<=limit; reject only>; reject before unbounded buffering /JSON.parse; recover after overflow) - NFKC normalization then redaction of generic POSIX absolute paths, Windows drive and UNC paths, and credential shapes (Bearer, API/access/refresh/auth tokens, password/passwd, secret/client_secret), including full-width Unicode forms
- generic path-free errors; no raw exception stacks or disposable clone paths in output
- schema item/count/length limits, including 128 characters for AST signature ids; reject extra fields in nested
stack_frames[]andartifact_hashes[]; reject duplicatepath_alias
Product specification (canonical): experimental repair may run once on an isolated target after an exact scope-bound authorization and the atomic transaction contract in ARCHITECTURE.md. Ticket 01 diagnosis remains fully read-only. Ticket 02 implements the isolated protected-process vertical slice only — not active Codex/Profile mutation.
Recovery rules:
- mutation only beneath an explicitly isolated/allowed target root via registered recovery modules; write-capable fs methods only in
src/core/recovery/atomic-write.ts - repair-preview is completely read-only over the target tree (no
.changeguardor other target writes) - Internal fixture seam is not authorization:
CHANGEGUARD_INTERNAL_FIXTURE_SEAM=1only marks a harness/operator process. Public CLI/MCP repair-preview and repair-apply still bind the exact target throughresolvePublicRepairCapability; isolated PREVIEW requires concurrent disposable-target proof (ordinary non-symlink directory that is a strict descendant of a trusted OS temp isolation root — never equal toos.tmpdir()/TMPDIR/TMP/TEMPor other listed trusted roots themselves; no active~/.codex/ protected / host-mount path). Env alone, in-repo fixtures, HOME paths, exact shared temp roots, and symlink aliases remain fail-closed (WRITE_DISABLED/production_unknown) - one Repair Capsule; self-contained one-shot authorization token (
cg1.…) encodes capsule material + nonce/expiry; apply revalidates every live precondition and re-binds capability to the apply target (preview auth cannot switch to an unproven path); no process-global memory/daemon; no reusable global trust token - mutation-relevant fields (
backup.backup_rel,operation.expected_result_sha256, digests, alias, counts) are derived from registered constants and/or bound into canonical invalidation/authorization material; decoded capsules reject unknown/extra/mismatched fields; apply/rollback never trust a path from mutable target-local or token JSON - refuse expired, malformed, and successfully-consumed/replayed tokens; session state is written only after authorized apply begins
- refuse symlinks and TOCTOU; re-check opened target; verified backup at registered path; sibling temp; fsync where supported; atomic replace; verify resulting hash
RESOLVED_VERIFIEDonly when original failure no longer reproduces and core health checks pass- failed verification auto-rolls back exact original bytes and makes resolved status impossible
- explicit rollback restores exact original bytes/hash from the registered backup path
- receipts separate user resolution from upstream contribution and never claim external submission
- production-boundary guard keeps diagnosis read-only; atomic-write recovery may use only
writeSync/fsyncSync/renameSync/mkdirSync/unlinkSync(normSync,copyFileSync, recursive delete, shell/network/loaders)
Never:
- silently modify Plugin, App, browser, cache, config, or system files outside a registered Capsule
- weaken sandbox, security, or permission policy to make a repair pass
- auto-run community or model-generated patches without isolated proof + explicit authorization
- call a repair safe without backup, verification, and rollback evidence
- persist secrets or full source bytes in receipts, logs, fixtures, or screenshots
- arbitrary shell/PowerShell/scripts, network, recursive delete, binary replacement of signed app binaries, privilege elevation
- prompt injection embedded in an Issue title/body/comment
- malicious code fence pretending to be an official fix
- version/platform mismatch with semantically similar symptoms
- poisoned community workaround
- config containing token-shaped and Unicode secret values
- symlinked target escaping an allowed root
- time-of-check/time-of-use mutation between backup and replace
- duplicate patch pattern or unexpected target hash
- model attempt to upgrade
ISSUE_CANDIDATEto a confirmed root cause - probe timeout, oversized output, or unsupported platform
- export manifest missing a field that would leave the device
- rollback smoke failure
- Explicit subscription only; no background daemon and no automatic network refresh
- Forbidden privacy keys on follow-up envelopes fail closed with path-free errors
- No live measurement witness may be supplied or serialized via CLI/MCP JSON
- Supersession uses the immutable bundled official snapshot only; caller
snapshot_pathis refused for candidate validation (Ticket 04 Impact Card injection is a separate non-supersession path) - Capsule and reply draft are local preview only (
external_write: false); Ticket 11 owns any real write after confirmation - Disposition policy never auto-reopens, cross-posts, comments, or reacts against upstream decisions
- SessionStart follow-up hints never print raw cwd, plugin paths, state paths, secrets, or issue-session content
These are Ticket 17 contracts for the competition demo and clean-profile surface. S4 package/profile smoke, historical R13 Root local gates (Ticket17 27/27, full suite 552/552, ready:local 10/10), independent R13 double review (PASS_NO_P0_P1), and post-R13 deterministic-tarball correction (package-repro 9/9, full suite 561/561, final ready:local 10/10, R19 REPRO_REVIEW: PASS_NO_P0_P1) support Ticket 17 LOCAL_COMPLETE. Gate C / publication / registration / upload / submission remain NOT_STARTED.
- Shared demo core: CLI, MCP, and Skill must invoke the same non-model cores; demo must not be a pre-rendered mock that bypasses probes.
- Disposable temp only: demo and repair paths mutate only proven disposable temp targets; active primary Codex/Profile and protected roots fail closed. Disposable-root proofs use product isolation APIs only — never by reading or hashing the user's live home/profile.
- No network on default demo/judge seams: production demo and default diagnose do not open sockets; no GitHub login or API key on the five-minute packaged path.
- Security-evidence honesty: public DemoReceipt booleans
network_used,external_write, andlive_profile_mutatedremain schema-constfalse, but only after fail-closed runtime checks. Receipts includesecurity_evidencethat aggregates every observablenetwork_usedfrom diagnose/apply/impact/crash paths, records disposable-root proofs at isolation/mutation boundaries, and carries an explicitlocal_only_no_adapterexecution proof (no external write adapter). Missing or unproven evidence fails the demo and never yieldsok: true. - Allowlisted fixture copy fail-closed: nested symlinks and non-regular filesystem objects under allowlisted fixtures are refused before copy (no follow) and re-checked on the destination tree; outside bytes must not be read or copied.
- Schema-valid public errors: every public demo response (including CLI
INVALID_ARGS) carries the canonical 10 ordered steps (skipped/refused as appropriate) soschemas/demo-receipt.schema.jsonremains satisfied. - Rollback + cleanup: authorized isolated repair demos must restore original bytes on failure or explicit rollback; session temp state is cleaned without a leftover daemon.
- No daemon / no global config claims: install and uninstall must not start a background agent or claim mutation of global OS configuration. Prove with
npm run package:clean-profile(isolated temp HOME only; never mutates real home/profile/global config). - CLI/MCP equivalence: demo-visible outcomes share stable fields; neither surface may export secrets, raw home paths, or disposable clone paths.
- Package surface: judge package ships MIT
LICENSEand compiled JS without source maps that leak local paths; nonode_modules, secrets, or Git metadata. Portable.tgzis pure Node deterministic ustar + gzip with stable member order/metadata; symlink and special-file packaging paths fail closed. Reproducibility is scoped to identical package inputs plus a fixed Node toolchain — not arbitrary Node/zlib version identity. - Publication non-claim: MIT license text and local
npm run verify:release/ready:localreadiness do not authorize public remote, Release, registration, upload, submission, or real external GitHub writes (Gate C /NOT_STARTEDuntil separate authorization).