Skip to content

feat(policy): gate credential-bearing file writes and .env as protected metadata - #4

Open
AtropinolTT wants to merge 1 commit into
NanmiCoder:mainfrom
AtropinolTT:feat/credential-write-guard-v2
Open

feat(policy): gate credential-bearing file writes and .env as protected metadata#4
AtropinolTT wants to merge 1 commit into
NanmiCoder:mainfrom
AtropinolTT:feat/credential-write-guard-v2

Conversation

@AtropinolTT

Copy link
Copy Markdown

Why: DESIGN.md lists "credentials, tokens, private keys" as protected material, but the workspace-local file-mutation fast path currently allows any content without classification. An agent (or prompt-injected repo) can write AKIA.../ghp_.../sk-.../BEGIN PRIVATE KEY material into a project file with no review.

What: containsSecretMaterial() matches strong credential/private-key patterns in file-mutation payloads (write/edit content fields and str_replace_editor old_str/new_str/file_text). Hits route to ask instead of the fast-path allow: classifier-eligible for write/edit (their content fields are redacted by the classifier sanitizer), direct ask for str_replace_editor (its payload fields are not covered by the sanitizer's content-key redaction, so the credential would otherwise reach the classifier in cleartext). .env and .env.* (multi-segment included) join isProtectedProjectPath, so env-file writes require explicit authorization.

Design notes: Weak patterns (password = "...") are deliberately excluded — they are routine in fixtures/templates, and classifier payloads already redact content fields. Credential hits are ask rather than hard deny, consistent with the DESIGN.md principle that blanket rules should not block targets the user explicitly authorized; str_replace_editor is the one exception (direct ask) purely because its payload fields are outside the sanitizer's redaction coverage.

Tests: table-driven policy tests covering every regex branch across write/edit and str_replace_editor (credential → ask; ordinary fixture write → allow; .env* → ask; multi-segment .env.development.local → ask). Full suite: 68/68 passing; tsc --noEmit clean (host + client).


Appendix — Known gaps and follow-up intent (PR-B)

This guard covers the file-mutation tools (write/edit/str_replace_editor). Two write paths remain outside its scope by design: shell redirection (echo "AKIA..." > .env) and patch-style tools (apply_patch). The shell-redirect path is the subject of a planned follow-up (PR-B): evaluate shell command segments for credential material in redirect targets (reusing containsSecretMaterial, no new patterns), submitted as a separate PR after this one lands and maintainer feedback is heard.

…ed metadata

containsSecretMaterial() matches strong credential/private-key patterns
(AKIA/ASIA, ghp_/gho_/ghu_/ghs_/github_pat_, sk-(proj-)?, and BEGIN
RSA|OPENSSH|EC|ECDSA|DSA|ENCRYPTED PRIVATE KEY blocks) in file-mutation
payloads - write/edit content fields and str_replace_editor
old_str/new_str/file_text. Hits route to ask instead of the fast-path
allow: classifier-eligible for write/edit (their content fields are
redacted by the classifier sanitizer), direct ask for str_replace_editor
(its payload fields are not covered by the sanitizer's content-key
redaction, so the credential would otherwise reach the classifier in
cleartext).

.env and .env.* (multi-segment included) join isProtectedProjectPath, so
env-file writes require explicit authorization. The str_replace_editor
secret check runs before the protected-path check so credential writes to
.env can never be routed to the classifier in cleartext.

Weak patterns (password = "...") are deliberately excluded - they are
routine in fixtures/templates. Credential hits are ask rather than hard
deny, consistent with the DESIGN.md principle that blanket rules should
not block targets the user explicitly authorized.

Tests: table-driven policy tests covering every regex branch across
write/edit and str_replace_editor (credential -> ask; ordinary fixture
write -> allow; .env* -> ask; multi-segment .env.development.local ->
ask). Full suite: 68/68 passing; tsc --noEmit clean (host + client).
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