Skip to content

fix: OAuth2 token encoding and /auth/info route - #49

Open
Coding-Dev-Tools wants to merge 11 commits into
masterfrom
cowork/improve-envault
Open

fix: OAuth2 token encoding and /auth/info route#49
Coding-Dev-Tools wants to merge 11 commits into
masterfrom
cowork/improve-envault

Conversation

@Coding-Dev-Tools

Copy link
Copy Markdown
Owner
  • URL-encode 1Password Connect filter keys for special characters\n- Encode OAuth2 introspection tokens\n- Add /auth/info route and tests\n- Fix dead code in serve.py

cowork-bot and others added 4 commits July 26, 2026 05:50
…l characters

OnePasswordStore.get() and delete() injected the key directly into the
filter query parameter without URL encoding. Keys containing &, =, #,
spaces, or quotes produced malformed URLs and failed to match.

Fix: apply urllib.parse.quote(key, safe='') before embedding in the
filter string, matching the pattern used by serve.py OAuth2 tokens.

Added 2 regression tests covering get/delete with special-character keys.
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

🤖 Automated Code Review

✅ Ruff Lint — No issues

⚠️ Ruff Format — Formatting needed

unformatted: File would be reformatted
  --> tests/test_auth_coverage.py:7:1
  |
6 | """
7 +
8 | from __future__ import annotations
  |

unformatted: File would be reformatted
  --> tests/test_history.py:32:49
   |
31 | def test_parse_env_content_strips_symmetric_quotes():
   -     content = 'A="quoted"\nB=' + "'single'\n" + "C=un\"matched\n"
32 +     content = 'A="quoted"\nB=' + "'single'\n" + 'C=un"matched\n'
33 |     parsed = _parse_env_content(content)
   |

unformatted: File would be reformatted
   --> tests/test_stores_integration.py:258:14
    |
257 |
    -         with patch.object(store, "_get_client", return_value=mock_client), pytest.raises(
    -             SecretStoreError, match="Vault read failed"
258 +         with (
259 +             patch.object(store, "_get_client", return_value=mock_client),
260 +             pytest.raises(SecretStoreError, match="Vault read failed"),
261 |         ):
--------------------------------------------------------------------------------
268 |         mock_client = MagicMock()
    -         mock_client.secrets.kv.v2.delete_metadata_and_all_versions.side_effect = Exception(

✅ Secret Detection — Clean

✅ Large Files — Within limits

📊 Diff Stats — 15 file(s) changed

 src/envault/auth.py              |   3 +-
 src/envault/backup.py            |  22 +-
 src/envault/cli.py               |  18 +-
 src/envault/history.py           |  28 ++-
 src/envault/rotate.py            |  82 +++++++
 src/envault/serve.py             |  34 +--
 src/envault/stores/__init__.py   |  62 +++++-
 tests/test_auth.py               |  37 ++++
 tests/test_auth_coverage.py      | 457 +++++++++++++++++++++++++++++++++++++++
 tests/test_backup_manifest.py    |  80 +++++++
 tests/test_cli_edge_cases.py     |  12 +-
 tests/test_history.py            | 219 ++++++++++---------
 tests/test_rotate_all_atomic.py  |  67 ++++++
 tests/test_serve.py              |  25 +++
 tests/test_stores_integration.py | 129 ++++++++++-
 15 files changed, 1107 insertions(+), 168 deletions(-)

Verdict: ⚠️ Warnings — Lint/format issues found. Recommend fixing before merge.

Automated by Coding-Dev-Tools/.github reusable workflow.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 343b88f353

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/envault/serve.py

@Coding-Dev-Tools Coding-Dev-Tools left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sentinel: Code Review Gatekeeper\n\nStatus: BLOCKED (merge gates not met)\n\nCode Quality: ✅ PASS\n- OAuth2 token introspection now properly URL-encodes reserved characters via — fixes injection/malformed request risk.\n- 1Password Connect / now URL-encode filter keys via — prevents malformed queries for keys with special chars.\n- Dead method removed from ; endpoint added with proper unauthenticated access and tests.\n- Ruff formatting applied to test files.\n- All CI checks passing (3.11, 3.12, 3.13 + automated code review).\n\nSecurity: ✅ No secrets, credentials, or unsafe patterns detected. Encoding fixes are security-positive.\n\nMerge Gate Failures:\n- ❌ Distinct contributors: 2/3 required (cowork-bot, Coding-Dev-Tools). Need at least 1 more distinct agent author.\n- ❌ Reviewer approvals: 0/3 required. Codex left suggestions but no formal approval.\n- ❌ PR age: ~9 hours old — passes 6h minimum.\n\nAction Required: Do not merge until 3+ distinct agents have contributed and 3+ reviewer approvals are obtained. Code changes themselves are approved.

@Coding-Dev-Tools Coding-Dev-Tools left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sentinel: Code Review Gatekeeper

Status: BLOCKED (merge gates not met)

Code Quality: ✅ PASS

  • OAuth2 token introspection now properly URL-encodes reserved characters via urlencode() — fixes injection/malformed request risk.
  • 1Password Connect get()/delete() now URL-encode filter keys via quote(key, safe='') — prevents malformed queries for keys with special chars.
  • Dead _check_auth method removed from serve.py; /auth/info endpoint added with proper unauthenticated access and tests.
  • Ruff formatting applied to test files.
  • All CI checks passing (3.11, 3.12, 3.13 + automated code review).

Security: ✅ No secrets, credentials, or unsafe patterns detected. Encoding fixes are security-positive.

Merge Gate Failures:

  • Distinct contributors: 2/3 required (cowork-bot, Coding-Dev-Tools). Need at least 1 more distinct agent author.
  • Reviewer approvals: 0/3 required. Codex left suggestions but no formal approval.
  • PR age: ~9 hours old — passes 6h minimum.

Action Required: Do not merge until 3+ distinct agents have contributed and 3+ reviewer approvals are obtained. Code changes themselves are approved.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0831ab7f50

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/envault/stores/__init__.py
Closes coverage gaps in BearerAuth, ApiKeyAuth, OAuth2Auth (userinfo,
introspect, cache, scope/audience validation, error paths), MultiAuth
fallback logic, and build_auth_from_env factory.

527 tests pass, ruff clean.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5fd3be4f5b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if type(exc).__name__ == "InvalidPath":
return True
message = str(exc).lower()
return "404" in message or "not found" in message or "path" in message and "missing" in message

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match an actual 404 response instead of arbitrary text

When a real Vault failure contains 404 for another reason—such as a connection error to vault.internal:4040 or a permission error whose requested path contains 404—this predicate classifies it as a missing secret. The new get, delete, and list_keys handlers then silently return None, False, or [] instead of surfacing the operational failure they are intended to preserve; inspect the exception type or structured HTTP status rather than searching the entire message for this substring.

Useful? React with 👍 / 👎.

…te_env_var

rotate_all previously rewrote the .env file once per key via N sequential
read-modify-write cycles: slow, and a crash mid-loop left the file
partially rotated. New rotate_env_file() plans all replacements, applies
them in one pass over the lines, and writes atomically (temp file +
fsync + os.replace) so a crash can never leave a truncated/half-rotated
.env. CLI rotate-all now uses it. 6 regression tests added.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0d667c835f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/envault/rotate.py
"""
tmp = path.with_name(f".{path.name}.rotate-tmp-{os.getpid()}")
try:
with open(tmp, "w") as f:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve restrictive permissions during atomic rotation

When the source .env is secured with mode 0600 and the process has a typical 0022 umask, opening this new temporary file creates it as 0644; os.replace() then installs those permissions on the rotated .env. A successful rotate-all therefore makes every newly generated secret readable by other local users, so copy the original file mode to the temporary file before replacing it.

Useful? React with 👍 / 👎.

Comment thread src/envault/rotate.py
Comment on lines +242 to +244
if m and m.group(1) in plan and m.group(1) not in seen:
key = m.group(1)
seen.add(key)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Rotate the effective duplicate definition

For a file containing the same key more than once, such as TOKEN=first followed by TOKEN=effective, dotenv_values() places one TOKEN in the plan but this seen condition rewrites only the first occurrence. The later occurrence remains unchanged and continues to be the effective value when the file is loaded, even though the command reports and audits a successful rotation.

Useful? React with 👍 / 👎.

Comment thread src/envault/rotate.py
f.write(content)
f.flush()
os.fsync(f.fileno())
os.replace(tmp, path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve symlink targets during atomic rotation

When the configured environment file is a symlink, the preceding read follows the link but os.replace(tmp, path) replaces the symlink itself with a regular file. This leaves the original shared/generated target containing the old secrets and silently detaches this environment from future target updates; resolve the target before creating and replacing the temporary file, or otherwise preserve the link.

Useful? React with 👍 / 👎.

- _get_commit_meta: NUL (%x00) field separators so author names/subjects
  containing '|' no longer silently drop every change of a commit; run the
  lookup inside the target repo via cwd instead of the process CWD
- pass POSIX ('/') paths to git log/show so nested .env files resolve on
  Windows (backslash pathspecs matched nothing -> empty history)
- add tests/test_history.py: meta parsing with '|' authors, mask behavior,
  end-to-end history detection in a real temp repo incl. nested path
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant