mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 09:40:12 +00:00
103 lines
4.0 KiB
Go
103 lines
4.0 KiB
Go
//go:build linux
|
|
|
|
package scenario
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/nezhahq/nezha/integration/agentcompat/internal/fixture"
|
|
)
|
|
|
|
func verifyMCPFilesystemPathGuards(ctx context.Context, assertions *AssertionSet, filesystem mcpFilesystemClient, fixtureParent string) error {
|
|
directTarget := filepath.Join(fixtureParent, "outside-sentinel")
|
|
symlinkParentTarget := filepath.Join(fixtureParent, "outside-directory", "parent-target.txt")
|
|
symlinkFinalTarget := filepath.Join(fixtureParent, "final-target.txt")
|
|
if err := os.Mkdir(filepath.Dir(symlinkParentTarget), 0o700); err != nil {
|
|
return err
|
|
}
|
|
for _, target := range []string{directTarget, symlinkParentTarget, symlinkFinalTarget} {
|
|
if err := os.WriteFile(target, []byte("unchanged:"+filepath.Base(target)), 0o600); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := os.Symlink(filepath.Dir(symlinkParentTarget), filepath.Join(filesystem.root.Absolute(), "linked-parent")); err != nil {
|
|
return err
|
|
}
|
|
if err := os.Symlink(symlinkFinalTarget, filepath.Join(filesystem.root.Absolute(), "linked-final")); err != nil {
|
|
return err
|
|
}
|
|
root, err := os.OpenRoot(fixtureParent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer root.Close()
|
|
targetNames := []string{"outside-sentinel", "outside-directory/parent-target.txt", "final-target.txt"}
|
|
before, err := filesystemTargetHashes(root, targetNames...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
requestsBefore := filesystem.client.RequestCount()
|
|
rejections := []struct {
|
|
want fixture.PathRejectionReason
|
|
run func() error
|
|
}{
|
|
{fixture.PathRejectionAbsolute, func() error { _, err := filesystem.read(ctx, directTarget, 0, 1, "utf8"); return err }},
|
|
{fixture.PathRejectionParent, func() error {
|
|
_, err := filesystem.write(ctx, mcpFilesystemWrite{relative: "../outside-sentinel", content: "changed", encoding: "utf8", mode: "0600"})
|
|
return err
|
|
}},
|
|
{fixture.PathRejectionVolume, func() error { _, err := filesystem.list(ctx, `C:\outside.txt`, false); return err }},
|
|
{fixture.PathRejectionSeparator, func() error { _, err := filesystem.read(ctx, `inside\outside.txt`, 0, 1, "utf8"); return err }},
|
|
{fixture.PathRejectionDestructiveRoot, func() error { _, err := filesystem.delete(ctx, ".", true); return err }},
|
|
{fixture.PathRejectionSymlinkParent, func() error {
|
|
_, err := filesystem.write(ctx, mcpFilesystemWrite{relative: "linked-parent/parent-target.txt", content: "changed", encoding: "utf8", mode: "0600"})
|
|
return err
|
|
}},
|
|
{fixture.PathRejectionSymlinkFinal, func() error { _, err := filesystem.delete(ctx, "linked-final", false); return err }},
|
|
}
|
|
matched := 0
|
|
for _, rejection := range rejections {
|
|
var pathError *fixture.AgentPathError
|
|
if errors.As(rejection.run(), &pathError) && pathError.Reason == rejection.want {
|
|
matched++
|
|
}
|
|
}
|
|
requestsAfter := filesystem.client.RequestCount()
|
|
dispatched := int64(requestsAfter) - int64(requestsBefore)
|
|
after, err := filesystemTargetHashes(root, targetNames...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := errors.Join(
|
|
os.Remove(filepath.Join(filesystem.root.Absolute(), "linked-parent")),
|
|
os.Remove(filepath.Join(filesystem.root.Absolute(), "linked-final")),
|
|
); err != nil {
|
|
return err
|
|
}
|
|
assertions.Record("fixture path rejections dispatch zero MCP HTTP requests", matched == len(rejections) && dispatched == 0, fmt.Sprintf("path_rejections_dispatched: %d; matched=%d total=%d requests_before=%d requests_after=%d", dispatched, matched, len(rejections), requestsBefore, requestsAfter))
|
|
assertions.Record("fixture path rejections leave outside and symlink targets unchanged", before == after, fmt.Sprintf("before=%x after=%x", before, after))
|
|
return nil
|
|
}
|
|
|
|
func filesystemTargetHashes(root *os.Root, names ...string) ([32]byte, error) {
|
|
hash := sha256.New()
|
|
for _, name := range names {
|
|
content, err := root.ReadFile(name)
|
|
if err != nil {
|
|
return [32]byte{}, err
|
|
}
|
|
if _, err := hash.Write(content); err != nil {
|
|
return [32]byte{}, err
|
|
}
|
|
}
|
|
var digest [32]byte
|
|
copy(digest[:], hash.Sum(nil))
|
|
return digest, nil
|
|
}
|