fix: test permission bits instead of comparing the secret file mode numerically - #206
Merged
EyJunge1 merged 1 commit intoJul 30, 2026
Merged
Conversation
`checkFilePermissions` used `mode > 0o600` to decide whether a `file://` secret is exposed. That compares a bitmask as an integer, which is unrelated to whether any group or other bit is set: a change in the owner field outweighs any change in the group/other fields. So 0404 (world-readable), 0006 (world-writable), 0007, 0060 and 0077 all warned about nothing, while 0640 -- which exposes strictly less than 0644 -- did warn, and 0700, which exposes nothing at all, warned needlessly. Six of eleven representative modes were classified wrongly. Mask with 0o077 and test for a non-zero result. Owner bits stay excluded, so 0600, 0400 and 0700 are all accepted; what matters is whether anyone else can reach the secret. Also zero-pad the mode in the message, since 0o006 rendered as "(6)" and the message is the only signal the user gets. The win32 early return is unchanged, and the check still only advises -- it never blocks a resolution that would otherwise succeed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #205
What was wrong
checkFilePermissionsdecided whether afile://secret is exposed withmode > 0o600— comparing a bitmask as an integer. Permission bits are three independent 3-bit fields, and their numeric ordering does not track how exposed a file is, because a change in the owner field outweighs any change in the group/other fields.0600rw-------0400r--------0700rwx------0644rw-r--r--0640rw-r-----0404r-----r--0060---rw----0006------rw-0007------rwx0077---rwxrwx0777rwxrwxrwxSix of eleven were classified wrongly. The sharpest pair:
0404is world-readable and was silent, while0640exposes strictly less and warned.resolveSecretValueis the accessor formemoryApiKey,embeddingApiKey,webServerAuthPasswordandwebServerApiToken(src/config.ts:563,:586,:620,:623). This warning is the only feedback a user gets that a credential file is reachable by other accounts on the machine.What changed
Owner bits stay excluded, so
0600,0400and0700are all accepted — the question is whether anyone else can reach the secret, which also removes the spurious0700warning.The message now zero-pads the mode.
0o006previously rendered as(6), and since the message is the entire signal the user receives it should name the mode unambiguously. It also says why — "granting group/other access".Deliberately unchanged: the
win32early return (POSIX mode bits are not meaningful there), and the fact that the check only advises. It must never block a resolution that would otherwise succeed, or a permission nit would take the plugin down — that has a test of its own.Scope note
This is advisory output, not an access control. The file is already readable by whoever the mode allows, and this change does not alter who can read it. What it fixes is a check that reported "fine" for the exact configuration it exists to flag.
Verification
bun test tests/secret-resolver-permissions.test.ts: 19 pass / 0 fail.src/services/secret-resolver.tsreverted tomainand the new tests kept: 8 fail / 11 pass. One of those failures is the false positive (0700warning needlessly), so the tests pin both directions.bun test: 297 pass / 9 fail vs baseline 278 / 9 — exactly +19, no new failures. (Those 9 are pre-existing here:plugin-loader-contractandplugin-bundle-boundaryrequire a builtdist/.)tsc --noEmit: clean.prettier --check: clean. The husky pre-commit hook ran both and passed.Tests added
tests/secret-resolver-permissions.test.ts— 19 cases, using thespyOn(fs, …)style already used intests/config-resolution.test.ts:it.eachover 5 owner-only modes (0600,0400,0200,0700,0000) asserting no warning — this is what catches the0700false positiveit.eachover 10 group/other-accessible modes asserting a warning, including the five that sort numerically below0o6000404vs0640pair asserted together, since the bug is most legible as a contradiction between them(006), not(6))win32still skips the check entirelystatSyncis mocked with the file-type bits set (0o100000 | mode) so that the& 0o777mask in the source is what strips them, rather than the test quietly handing over pre-masked input.