Skip to content

install.sh: please add a Codex opt-out flag, and support credential-free attestation verification #1178

Description

@astradevkin

Two related install-time requests, both from running plannotator on several machines where the installer's defaults don't quite fit. Both currently need workarounds that I'd rather not maintain, and one of them is fragile in a way that will break silently.

Happy to open these separately if you'd prefer — they're grouped because they're both install.sh behaviour.


1. An explicit way to opt out of the Codex integration

What happens today: the installer auto-detects Codex and writes to ~/.codex/hooks.json and ~/.codex/config.toml. Detection is command -v codex or codex_home_has_user_config (any content under $CODEX_HOME).

There's no way to decline. --no-extras doesn't cover it, and there's no PLANNOTATOR_SKIP_CODEX_INSTALL to match the existing PLANNOTATOR_SKIP_SEM_INSTALL / PLANNOTATOR_SKIP_AGENT_TERMINAL_INSTALL.

Why that's a problem: ~/.codex/hooks.json isn't necessarily plannotator's to edit. On my machines it already holds my own pre_tool_use, user_prompt_submit and stop hooks, with other tooling that monitors it. I use plannotator via Claude Code and don't want the Codex integration at all — but I can't say so.

The workaround, which I'd like to delete: run the installer with /opt/homebrew/bin removed from PATH and CODEX_HOME pointed at an empty temp directory, so both detection legs fail:

SANDBOX=$(mktemp -d) && ln -s "$(command -v gh)" "$SANDBOX/gh"
env CODEX_HOME="$(mktemp -d)" PATH="$SANDBOX:/usr/bin:/bin:/usr/sbin:/sbin" \
    bash install.sh --no-extras

It works — output reads Codex was not detected. — but it works by lying to the installer about the machine. The day a third detection leg is added, it fails silently: I'd get a Codex-wired install and the same reassuring "not detected" message. At roughly a release every three days, that's a real bet. I currently defend against it with a mandatory pre/post checksum assertion on both Codex files, which is a lot of machinery to avoid an env var.

It also, reasonably, trips security tooling: "execute a freshly downloaded script with a synthesised PATH shadowing a real binary" is a fair description of a malware installer. Mine flagged it, correctly.

Ask: PLANNOTATOR_SKIP_CODEX_INSTALL=1, or --skip-codex, matching the naming of the two skip vars that already exist. Same for the other agent integrations if it's cheap — Gemini, Kiro, OpenCode.


2. Provenance verification requires a credential it doesn't actually need

What happens today: install.sh verifies provenance with

gh attestation verify "$tmp_file" --repo "$REPO" --source-ref "refs/tags/$latest_tag" \
   --signer-workflow ".../release.yml"

With no --bundle, gh requires an authenticated login. Without it the install fails closed — which is correct behaviour, and I'm glad it's fail-closed.

Why the credential is a real cost: this repo is public, and its attestations endpoint is world-readable:

D=$(shasum -a 256 ./plannotator | awk '{print $1}')
curl -s -o /dev/null -w '%{http_code}\n' \
  "https://api.github.com/repos/<owner>/<repo>/attestations/sha256:$D"
# 200, no auth

Fetch the bundle and pass --bundle and the same verification passes with no credential at all — I've confirmed this under the full constraint set (--repo + --source-ref + --signer-workflow), with GH_CONFIG_DIR pointed at an empty directory and every GH_*_TOKEN unset, on three machines. Five negative controls (wrong binary, wrong owner, wrong source-ref, wrong signer-workflow, wrong repo) all correctly fail.

There's no security downgrade in doing it that way: the authenticated path fetches the bundle from the same endpoint. The only difference is an Authorization header on the same request to the same host.

The cost of requiring the login is concrete. On a headless or SSH-only machine, gh auth login can't reach the login keychain and stores the token in plain text, and the default web flow grants workflow scope — write access to .github/workflows/, i.e. arbitrary Actions execution with those repos' secrets. So a user who simply follows the install instructions on a remote box ends up with a broadly-scoped plaintext token on disk solely to read public data. That seems like a steep price for a verification step.

Ask: have install.sh fetch the attestation bundle from the public endpoint and verify with --bundle, falling back to the current path if the fetch fails. That would make verification work unauthenticated everywhere, and let users who don't want gh logged in keep provenance checking on rather than reaching for --skip-attestation.

Two things worth knowing if you implement it:

  • The Sigstore trusted root is fetched via TUF on every run — not embedded, not cached (an empty GH_CONFIG_DIR stays empty after a successful verify). So verification still needs network access and fails closed offline with no valid Sigstore verifiers could be initialized. Worth surfacing as a distinct message from "provenance failed", since they mean very different things.
  • The unauthenticated API is 60 requests/hour per IP. Fine at install cadence, but shouldn't be retried in a loop.

For context on why I care about both: I run this with the plugin/hook deliberately not installed, invoked only by name, on machines where I need the install to be reproducible and the update path to be safe to run unattended. Both requests are about making the supported path fit that, rather than maintaining workarounds around it. The tool itself has been excellent — the annotate flow is exactly what I wanted.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions