Files
gnoma/internal/config
vikingowl 3eeb5b46d7 feat(safety): pre-launch cwd classifier + context banner
Implements S-1 through S-7 of the startup-safety-banner plan.

Adds a pre-launch safety check that classifies the current working
directory into three tiers and gates the launch:

  TierRefuse  /, /etc, /sys, /proc, /usr, /var, /bin, /sbin, /boot,
              /root, /dev (Linux) and /System, /Library, /private,
              /Applications (macOS). Refuses with exit 2 unless
              --dangerously-allow-anywhere is passed.

  TierWarn    $HOME, ~/Desktop, ~/Downloads, ~/Documents, ~/.config,
              ~/.local, ~/.cache, /tmp, and similar dumping grounds.
              Prints a banner and reads a single y/Y from stdin to
              confirm; any other input (or EOF, including piped/
              scripted invocation) aborts with exit 1.

  TierOK      Anywhere with a recognized project marker (.gnoma/,
              go.mod, package.json, pyproject.toml, Cargo.toml,
              Makefile, Dockerfile, build.gradle*, pom.xml) or
              inside a git repo. No prompt; banner only.

Project markers and git-repo presence override the TierWarn check —
a project dir inside $HOME stays TierOK. The require_project_marker
config knob can flip that for strict users.

Container detection: when /.dockerenv or /run/.containerenv exists,
TierRefuse downgrades to TierWarn (devcontainers often chroot to /
or similar). Best-effort; false positives only soften the gate.

The context banner is always rendered (TierOK, TierWarn, TierRefuse
alike) and summarizes: cwd, git branch + dirty state, project type,
provider/model, modes (permission, incognito, prefer), and a
top-level sensitive-file inventory. Inventory matches .env,
.env.*, env.local; private-key extensions (.pem, .key, .crt, .p12,
.pfx); SSH key names (id_rsa, id_ed25519, ...); credentials files;
.netrc / .pgpass; KeePass vaults; and .ssh/ .aws/ .kube/ .gcloud/
.azure/ .docker/ directories. Precision-tested: .envrc and
secret_handler.go do NOT match. Bounded at 1000 entries.

Architecture:
- internal/safety/cwd.go — Classification + symlink-resolving tier
  classifier with platform-specific roots and container detection.
- internal/safety/sensitive.go — pattern-based top-level scanner,
  deterministic ordering, scanLimit guard against pathological dirs.
- internal/safety/banner.go — pure render functions for the warn
  prefix, refuse message, and context banner. Safe for golden-string
  testing.
- internal/config/config.go — new [safety] section with three
  config keys, defaults applied via ResolvedSafety() helper. Pointer
  fields distinguish "user omitted" from "user set to false."
- cmd/gnoma/main.go — gate runs after subcommand dispatch (so
  `gnoma providers / profile / slm / router` skip the prompt) and
  before provider creation. --dangerously-allow-anywhere bypasses
  the gate with an explicit log warning.

The runtime keypress reads up to 8 bytes from os.Stdin and accepts
only "y" / "Y" trimmed; EOF returns false (piped invocations
without the flag will abort). Documented in the readYesConfirmation
helper. Manual smoke (per plan):
  - `cd / && gnoma -p test` → refuses
  - `cd ~ && gnoma` → warns + keypress
  - `cd ~/git/some-repo && gnoma` → banner only
  - subcommands skip the gate entirely

Linux + macOS classification; Windows path handling deferred per
plan (treated as TierOK there until follow-up).

Refs: docs/superpowers/plans/2026-05-23-startup-safety-banner.md
2026-05-23 22:19:39 +02:00
..