Skip to content

Cloud sweep: mask secret-looking prompts, #16 split, skills + AI-first README, sdist skill fix, PyPI metadata - #31

Merged
thorwhalen merged 6 commits into
masterfrom
cloud-sweep-2026-09-26
Sep 27, 2026
Merged

thorwhalen merged 6 commits into
masterfrom
cloud-sweep-2026-09-26

Conversation

@thorwhalen

@thorwhalen thorwhalen commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

Tests

Command, matching CI: python -m pytest config2py --doctest-modules -o doctest_optionflags='ELLIPSIS IGNORE_EXCEPTION_DETAIL' -q

passed failed skipped
Baseline (master 10e9005) 132 0 4
Final (3c54bef) 155 0 5
  • The extra skip is test_packaging.py, which needs hatchling. With hatchling installed the suite gives 156 passed, 4 skipped.
  • The suite also passes on Python 3.10, 3.11, 3.12 and 3.13.
  • wads ci-local passes: ruff, tests on 3.10 and 3.12, and uv build.
  • Hosted CI on 3c54bef is green: Validation 3.10 and 3.12, plus Windows.

Dependents were run against the working tree before and after each code change:

Dependent Baseline Final
i2mint/py2store 80 passed, 1 skipped 80 passed, 1 skipped
i2mint/xdol 61 passed, 2 failed 61 passed, 2 failed
thorwhalen/oa 41 passed, 3 failed 41 passed, 3 failed

The xdol and oa failures exist on master too. xdol's two are doctest-format failures. oa's three need a real OpenAI key.

Changes

  • docs (Minor audit findings (omnibus): docstring overclaims, broad fallback, pickle codec, import-time side effects #16): pickle-decode security warnings, and a warning about the broad (Exception,) fallback. Docstrings now state the silent-empty behaviours for missing paths, the os.path.sep detection and the import-time folder creation. Removed dead commented-out code. No behaviour change.

  • fix (Default mask_input=False echoes secrets to terminal in simple_config_getter flow #13): DFLT_MASKING_INPUT is now looks_like_secret, so secret-looking prompts are masked and other prompts still echo. mask_input accepts a bool or a prompt -> bool predicate.

    • When masking is inferred and stdin is piped, the answer is read from stdin, because stdlib getpass would read /dev/tty. Jupyter's replacement getpass is always honoured.
    • Verified under a real pty: typed secrets no longer appear in terminal output. 18 tests in test_masking.py.
  • packaging: SPDX license = "Apache-2.0" plus license-files, classifiers for 3.10 to 3.13, keywords, author, and the Documentation, Repository and Issues URLs. hatchling>=1.27 for PEP 639. twine check passes, and CI's version-bump regex still hits [project].version only.

  • agent layer:

    • A consumer skill, config2py/data/skills/config2py-quickstart, which ships in the wheel.
    • A dev skill, skills/config2py-dev, plus relative symlinks in .claude/skills/. Both skills pass skill.validate.
    • An sdist fix: hatchling follows symlinks and skips folders it has already visited, so without exclude = [".claude/skills"] and skip-excluded-dirs = true the real skill folders were dropped from the sdist, and so from the wheel. test_packaging.py guards this.
    • .claude/CLAUDE.md is updated.
  • docs (README SyncStore examples fail as written (missing file, missing key_path, wrong import) #32): the README is now AI-first:

    • a link to the human section at the end;
    • "What an agent can do", with a runnable example;
    • a regenerated epythet agent section;
    • fixed SyncStore examples.

    test_docs_examples.py runs every Python block and >>> example of the README and the consumer skill in a sandboxed HOME.

  • fix (from review): an explicit mask_input=True keeps reading the terminal when stdin is piped, as on master.

Issues

Not done, and why

Adversarial review (Opus subagent)

Round 1: BLOCK. Findings, and what I did about each:

  1. Blocking. Explicit mask_input=True read piped stdin instead of the terminal, so the first piped line was saved as the secret. Fixed in 3c54bef: the stdin fallback now applies only when masking is inferred. A new test failed before the fix, and a real pty check now matches master.
  2. Dependents' tests that patch only builtins.input hang under pytest -s in a terminal when they ask for a secret-looking key. Captured runs and CI are fine. Release note below.
  3. looks_like_secret is a plain substring match, so KEYS_DIR and AUTHOR are masked too. Documented in the docstring and the skill. I kept the design, since masking too much is the safer error.
  4. The piped-stdin wording in the docs. Updated.
  5. IDLE started from a terminal now prompts in that terminal for secret-looking keys. Release note below.

Round 2 (3c54bef): APPROVE. The fix was verified, and the toggle path behaves like master. Its one non-blocking note is that toggling back to masked uses getpass, which is master behaviour.

For whoever lands it

  • Release note: prompts whose text contains key, token, pass, pwd, secret, api, auth, credential or private are now masked by default. Pass mask_input=False to echo them. Tests that patch only builtins.input should also patch getpass.getpass (see config2py.tests.utils_for_testing.user_input_patch).
  • This PR also touches pyproject.toml, so expect a small conflict with chore: remove vestigial setup.cfg, add [tool.wads.ci], migrate CI to the stub #22.

Fixes #13
Fixes #16
Fixes #32

🤖 Generated with Claude Code

…es); drop dead code

Documentation-only half of the #16 audit omnibus. No behaviour change.

- codecs: warn that .pkl/.pickle decode with pickle.loads (arbitrary code
  execution on untrusted bytes); replace the eval() in register_codec's example.
- base: get_config docstring warns about the broad (Exception,) default;
  remove the dead commented-out OPENAI_API_KEY/getpass block.
- tools/s_configparser: state the current silent-empty behaviour of
  extract_exports and ConfigReader for missing paths, the os.path.sep-based
  path detection, and the import-time folder creation.

The behaviour changes are split into #25, #26, #27, #28 and #29.

Fixes #16

Copy link
Copy Markdown
Member Author

cloud-status: started — baseline 132/0/4 (dependents: py2store 80/0/1, xdol 61/2, oa 41/3, all failures pre-existing); plan: #13 key-aware masking plus a piped-stdin guard, #16 docs split (done, filed #25-#29), #12 analysis comment, consumer and dev skills plus AI-first README.


Generated by Claude Code

…sking

ask_user_for_input (and so the simple_config_getter/config_getter
prompt-for-missing-key flow) echoed every typed value, secrets included.

- DFLT_MASKING_INPUT is now looks_like_secret: prompts mentioning
  secret/token/pass/pwd/api/key/credential/auth/private are masked, others
  (file paths, names) still echo. mask_input accepts a bool or a
  prompt -> bool predicate; explicit True/False behave as before.
- Masked reads use input() when stdin is not a terminal and getpass is the
  stdlib one: stdlib getpass reads /dev/tty, not stdin, so piped input was
  ignored (or the call hung). A frontend's replacement getpass (Jupyter's
  masked widget) is always used.

Tests: config2py/tests/test_masking.py (17). Verified under a real pty that
typed secrets no longer appear in terminal output, and that piped input with
a controlling tty is read instead of hanging. Dependents py2store, xdol and oa
match their baselines.

Fixes #13
- license = "Apache-2.0" (PEP 639) plus license-files, replacing the
  deprecated [project.license] table; no License :: classifier.
- Classifiers for Python 3.10-3.13 (suite verified on each), keywords,
  author, and Documentation/Repository/Issues URLs (docs site checked live).
- build-system floor hatchling>=1.27, the first release with PEP 639 support.

Dependencies, version and CI are unchanged; setup.cfg removal and the CI
stub migration stay in #22. twine check passes on the sdist and wheel, and
the CI version-bump regex still targets [project].version only.
- config2py/data/skills/config2py-quickstart: how to use the package (entry
  points, get_config, simple_config_getter, user_gettable, app folders,
  FileStore, ConfigStore, codecs, gotchas). Ships in the wheel. Every Python
  snippet was run.
- skills/config2py-dev: how to work on it (module map, CI-matching test
  command, test isolation, dependents gate, open design issues, release flow).
- .claude/skills/<name>: relative symlinks so Claude Code loads both.
- pyproject: [tool.hatch.build.targets.sdist] excludes .claude/skills with
  skip-excluded-dirs. hatchling walks with followlinks=True and skips inodes
  it has seen, so the symlinks made it drop config2py/data/skills from the
  sdist, and so from the wheel CI builds from it. test_packaging.py checks
  the sdist file list whenever hatchling is importable.
- .claude/CLAUDE.md: agent-layer notes, the #16 follow-up issues, the
  dependents gate, and current test counts.

Both skills pass skill.validate with no issues.
…amples

- Top: what the package does, a link to the human section at the end, then
  "What an agent can do" with a minimal runnable example and the pip route to
  the bundled skill. The epythet agentic section was regenerated with
  `epythet ai-readme-check . --write` and now lists the skills and CLAUDE.md.
- Fixed examples: FileStore on a fresh file and nested key_path need
  create_file_content / create_key_path_content, the register_extension import
  is now config2py.sync_store, placeholder functions are replaced by runnable
  ones, and a duplicated paragraph is removed. The prompt now documents the
  masking behaviour.
- New section at the end for human developers: dev setup, design rationale,
  contributing, where to ask.
- config2py/tests/test_docs_examples.py runs every python block and >>>
  example of README.md and of the consumer skill, in a subprocess with a
  sandboxed HOME and closed stdin. Blocks that must prompt carry a
  <!-- no-test --> marker. The old README examples fail this test.

Fixes #32
The adversarial review of #31 found a regression: the stdin fallback applied
to every masked read, so an explicit mask_input=True with a terminal present
and stdin piped read the first piped line as the secret. Stdlib getpass reads
/dev/tty on purpose there, as sudo does. The fallback now applies only when
masking was inferred by a predicate (the new default), which keeps both
paths identical to master: explicit True reads the terminal, and a defaulted
prompt reads stdin as the old echoing default did. Verified under a real pty.

The docstrings now also say that looks_like_secret is a substring match on
the whole prompt.

Refs #13
@thorwhalen thorwhalen changed the title WIP: Cloud sweep (2026-09-26) Cloud sweep: mask secret-looking prompts, #16 split, skills + AI-first README, sdist skill fix, PyPI metadata Sep 27, 2026
@thorwhalen
thorwhalen merged commit 5e7e436 into master Sep 27, 2026
12 checks passed
@thorwhalen
thorwhalen deleted the cloud-sweep-2026-09-26 branch September 27, 2026 09:07

Copy link
Copy Markdown
Member Author

cloud-status: done — tests 132/0/4 → 155/0/5 (dependents unchanged); closed #13, #16, #32; filed #25-#30, #33; needs-local #12, #23, #28; merged as 5e7e436, 0.1.55 published to PyPI with master CI all green.


Generated by Claude Code

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

Labels

None yet

Projects

None yet

1 participant