Skip to content

sandbox-lint's path normaliser strips only one trailing slash, so a double-slash credential path bypasses the M.29 check #1148

Description

@AmirF194

What's broken

tools/sandbox-lint's _normalise() strips a single trailing slash before comparing a .claude/settings.json filesystem path against FORBIDDEN_ALLOW_READ / FORBIDDEN_ALLOW_WRITE (mitigation M.29). A path with two or more trailing slashes, e.g. ~/.ssh//, only loses one, so check_invariants() reports no violation for an allowRead/allowWrite entry that in practice still grants access to a credential directory.

Which layer

tools/sandbox-lint/src/sandbox_lint/__init__.py, _normalise() (line 123) and every invariant check that consumes it through _normalised_set().

How to reproduce

from sandbox_lint import check_invariants
settings = {
    "sandbox": {
        "enabled": True,
        "filesystem": {
            "denyRead": ["~/"],
            "allowRead": ["~/.ssh//"],
            "allowWrite": [],
        },
    },
    "permissions": {"deny": [...]},  # the full REQUIRED_PERMISSIONS_DENY set
}
print(check_invariants(settings))  # []

Expected vs actual

Expected: an allowRead entry of ~/.ssh// is caught the same way ~/.ssh and ~/.ssh/ already are, since it names the same directory.

Actual: check_invariants() returns an empty error list, so the credential-path check silently passes.

Surface area

Same gap applies to denyRead and allowWrite. I have a fix ready (_normalise stripping all trailing slashes instead of one) with regression tests, opening as a PR against this issue.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions