Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

set up webassembly runtime #363

Open
wants to merge 1,811 commits into
base: main
Choose a base branch
from
Open

set up webassembly runtime #363

wants to merge 1,811 commits into from

Conversation

Southclaws
Copy link
Owner

No description provided.

Southclaws and others added 30 commits November 11, 2024 10:53
search should exist on its own and it's always available as a feature so this makes dealing with enabled/disabled semdex simpler by removing the checks from the semdex impl and reducing the complexity of re-implementing the semdexer interface
Update module github.com/go-resty/resty/v2 to v2.16.0
Southclaws and others added 26 commits January 11, 2025 17:18
- currently only applies to single thread get
- still exploring internal API design for this
- will maybe look at proper caching layer too
- server fetch is force-cache, 60s revalidation
- client fetch is default behaviour which will trigger HTTP Conditional Requests
apply cache headers to some most used routes
Copy link

vercel bot commented Jan 31, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

2 Skipped Deployments
Name Status Preview Comments Updated (UTC)
storyden ⬜️ Ignored (Inspect) Jan 31, 2025 7:39pm
storyden-homepage ⬜️ Ignored (Inspect) Visit Preview Jan 31, 2025 7:39pm

Copy link

coderabbitai bot commented Jan 31, 2025

📝 Walkthrough

Walkthrough

This pull request introduces a comprehensive plugin management system in Go, focusing on WebAssembly (Wasm) plugin handling. The implementation spans multiple packages including plugin, plugin_reader, plugin_writer, and plugin_manager. The system provides functionality for validating, listing, creating, and potentially managing plugins, with support for both local and S3 storage backends. A new wrun package is also added to handle Wasm binary execution using the wazero runtime.

Changes

File Change Summary
app/resources/plugin/plugin.go Added plugin package with types for binary validation, metadata, and plugin management
app/resources/plugin/plugin_reader/reader.go Implemented plugin listing functionality with error handling
app/resources/plugin/plugin_writer/writer.go Added plugin creation and storage mechanism
app/services/plugin/plugin_manager/manager.go Created initial plugin manager struct
internal/infrastructure/object/* Extended Storer interface and implementations with List method
internal/infrastructure/wrun/wasm.go Introduced WebAssembly runner with execution capabilities

Sequence Diagram

sequenceDiagram
    participant Writer
    participant Store
    participant Runner
    participant Reader
    
    Writer->>Runner: Validate Plugin Binary
    Runner-->>Writer: Return Metadata/Error
    Writer->>Store: Write Plugin
    Reader->>Store: List Plugins
    Store-->>Reader: Return Plugin Files
    Reader->>Runner: Validate Each Plugin
    Runner-->>Reader: Return Plugin Metadata
Loading

Possibly related PRs

Poem

🐰 Plugins dance in WebAssembly's light,
Bytes of code take magical flight
Validation's key, metadata's bright
A rabbit's dream of digital might
Wazero runtime, our plugin's delight! 🚀

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (6)
internal/infrastructure/wrun/wasm.go (1)

91-96: Check scanner errors for reliability.
The scanner loop does not check scanner.Err() to detect read failures or partial lines. Handling possible I/O errors could help diagnose runtime issues.

You can add an error check right after the loop:

 for s.Scan() {
   outputs = append(outputs, s.Bytes())
 }
+if s.Err() != nil {
+  return nil, fault.Wrap(s.Err(), fmsg.With("error reading output from module"))
+}
internal/infrastructure/wrun/testdata/test2.go (1)

12-20: Consider handling scan errors.
Currently, the code returns immediately if s.Scan() is false, skipping potential error details. Checking s.Err() could improve debugging in case of input read failures.

You could do:

 if !s.Scan() {
-  return
+  if err := s.Err(); err != nil {
+    fmt.Fprintln(os.Stderr, "Error reading input:", err)
+  }
+  return
 }
internal/infrastructure/object/object.go (1)

16-16: Provide pagination or filtering options for scalability.
When listing objects using a prefix, the list could grow large. Consider a paginated or stream-based approach in future iterations for systems handling large numbers of keys to avoid potential memory overhead.

app/resources/plugin/plugin.go (2)

28-31: Consider adding validation tags to Metadata struct.

Adding validation tags would make the requirements more explicit and enable using validation libraries.

 type Metadata struct {
-	Name    string
-	Version string
+	Name    string `validate:"required"`
+	Version string `validate:"required,semver"`
 }

38-42: Consider adding validation state to Available struct.

The Error field alone might not provide enough context about the validation state.

 type Available struct {
 	StoragePath string
 	Loaded      *Package
 	Error       error
+	State       ValidationState
 }

+type ValidationState string
+
+const (
+	ValidationStateUnknown   ValidationState = ""
+	ValidationStateValid     ValidationState = "valid"
+	ValidationStateInvalid   ValidationState = "invalid"
+	ValidationStateError     ValidationState = "error"
+)
app/resources/plugin/plugin_reader/reader.go (1)

32-66: Consider adding concurrent processing for better performance.

The List method could benefit from parallel processing when dealing with multiple plugins.

+func (r *Reader) List(ctx context.Context) ([]*plugin.Available, error) {
+	files, err := r.store.List(ctx, plugin.PluginDirectory)
+	if err != nil {
+		return nil, fault.Wrap(err, fctx.With(ctx))
+	}
+
+	// Process plugins concurrently with a worker pool
+	const maxWorkers = 4
+	sem := make(chan struct{}, maxWorkers)
+	var wg sync.WaitGroup
+	ps := make([]*plugin.Available, len(files))
+
+	for i, s := range files {
+		wg.Add(1)
+		go func(i int, s string) {
+			defer wg.Done()
+			sem <- struct{}{} // Acquire
+			defer func() { <-sem }() // Release
+
+			// ... rest of the processing logic ...
+		}(i, s)
+	}
+
+	wg.Wait()
+	return ps, nil
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 500e24e and 11c2808.

⛔ Files ignored due to path filters (4)
  • go.mod is excluded by !**/*.mod
  • go.sum is excluded by !**/*.sum, !**/*.sum
  • internal/infrastructure/wrun/testdata/test1.wasm is excluded by !**/*.wasm, !**/*.wasm
  • internal/infrastructure/wrun/testdata/test2.wasm is excluded by !**/*.wasm, !**/*.wasm
📒 Files selected for processing (11)
  • app/resources/plugin/plugin.go (1 hunks)
  • app/resources/plugin/plugin_reader/reader.go (1 hunks)
  • app/resources/plugin/plugin_writer/writer.go (1 hunks)
  • app/services/plugin/plugin_manager/manager.go (1 hunks)
  • internal/infrastructure/object/local.go (1 hunks)
  • internal/infrastructure/object/object.go (1 hunks)
  • internal/infrastructure/object/s3.go (1 hunks)
  • internal/infrastructure/wrun/testdata/test1.go (1 hunks)
  • internal/infrastructure/wrun/testdata/test2.go (1 hunks)
  • internal/infrastructure/wrun/wasm.go (1 hunks)
  • internal/infrastructure/wrun/wasm_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • app/services/plugin/plugin_manager/manager.go
  • internal/infrastructure/wrun/testdata/test1.go
🔇 Additional comments (6)
internal/infrastructure/wrun/wasm.go (2)

20-22: Looks good on constructor.
No issues found with this constructor; it appears clean and straightforward.


28-36: Wazero runtime initialization looks fine.
Instantiating the WASI snapshot is done correctly. This is well-aligned with typical wazero usage.

app/resources/plugin/plugin.go (1)

14-26: Add error handling for invalid metadata JSON format.

The Validate method should verify the required fields in metadata after JSON unmarshaling to ensure plugin validity.

Add validation after unmarshaling:

 func (b Binary) Validate(ctx context.Context, r wrun.Runner) (*Metadata, error) {
 	mb, err := r.RunOnce(ctx, b, nil)
 	if err != nil {
 		return nil, err
 	}

 	var m Metadata
 	if err = json.Unmarshal(mb, &m); err != nil {
 		return nil, err
 	}

+	if m.Name == "" || m.Version == "" {
+		return nil, fmt.Errorf("invalid plugin metadata: name and version are required")
+	}
+
 	return &m, nil
 }
✅ Verification successful

The review comment is correct - Name and Version fields must be validated

The Metadata struct requires both Name and Version fields, but the current implementation doesn't validate them after JSON unmarshaling. The suggested validation is necessary to ensure plugin validity.

🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts found


Script:

#!/bin/bash
# Find Metadata struct definition
ast-grep --pattern 'type Metadata struct {
  $$$
}'

# Search for any existing validation patterns
rg "func.*Validate.*Metadata" -A 5

# Look for Metadata usage
rg "Metadata" -B 2 -A 2

Length of output: 74949

internal/infrastructure/wrun/wasm_test.go (2)

12-28: Add error test cases for RunOnce.

The test only covers the happy path. Add test cases for invalid WASM binaries and execution failures.

+func Test_wazeroRunner_RunOnce_InvalidWASM(t *testing.T) {
+	r := require.New(t)
+	ctx := context.Background()
+	runner := newWazeroRunner(ctx)
+
+	_, err := runner.RunOnce(ctx, []byte("invalid wasm"), nil)
+	r.Error(err)
+}
✅ Verification successful

The review comment is correct - error test cases are needed

The implementation has multiple error paths that are currently untested:

  • Invalid WASM binary handling
  • Module instantiation failures
  • No output scenarios
  • Command encoding errors

The suggested test case in the review comment would help improve coverage of error scenarios.

🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts found


Script:

#!/bin/bash
# Find implementation and related test files
fd wasm.go
fd wasm_test.go

# Look for error handling in implementation
ast-grep --pattern 'func ($runner *wazeroRunner) RunOnce($ctx context.Context, $data []byte, $input interface{}) ([]byte, error) {
  $$$
}'

# Check test fixtures
ls -la internal/infrastructure/wrun/testdata/

Length of output: 667


Script:

#!/bin/bash
# Check implementation file for error handling
cat internal/infrastructure/wrun/wasm.go

# Look for existing error test cases
cat internal/infrastructure/wrun/wasm_test.go

# Check test fixture source
cat internal/infrastructure/wrun/testdata/test1.go
cat internal/infrastructure/wrun/testdata/test2.go

Length of output: 3578


30-50: Add timeout test case for RunOnce.

Test should verify that the context timeout is respected during WASM execution.

+func Test_wazeroRunner_RunOnce_Timeout(t *testing.T) {
+	r := require.New(t)
+	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
+	defer cancel()
+
+	runner := newWazeroRunner(ctx)
+	f, err := os.ReadFile("./testdata/infinite_loop.wasm")
+	r.NoError(err)
+
+	_, err = runner.RunOnce(ctx, f, nil)
+	r.ErrorIs(err, context.DeadlineExceeded)
+}
✅ Verification successful

Modify timeout test to use existing WASM file

The timeout test suggestion is valid, but needs to use an existing WASM file (test1.wasm or test2.wasm) instead of the non-existent infinite_loop.wasm. The test can still verify timeout behavior with a very short timeout duration:

func Test_wazeroRunner_RunOnce_Timeout(t *testing.T) {
	r := require.New(t)
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
	defer cancel()

	runner := newWazeroRunner(ctx)
	f, err := os.ReadFile("./testdata/test1.wasm")
	r.NoError(err)

	_, err = runner.RunOnce(ctx, f, nil)
	r.ErrorIs(err, context.DeadlineExceeded)
}
🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts found


Script:

#!/bin/bash
# Check if infinite_loop.wasm exists
fd infinite_loop.wasm

# Check RunOnce implementation and existing timeout tests
ast-grep --pattern 'func (r *wazeroRunner) RunOnce(ctx context.Context, $$$) {
  $$$
}'

# Search for timeout-related tests
rg -A 5 "context.*WithTimeout.*wazero" --type go

Length of output: 164


Script:

#!/bin/bash
# Find all WASM files
fd .wasm

# Check the implementation file
rg -A 10 "type wazeroRunner struct" --type go

# Look for RunOnce implementation with broader pattern
rg -A 10 "func.*RunOnce.*context\.Context" --type go

# Find all test files in the same directory
fd ".*_test\.go$" --type f

Length of output: 3667

internal/infrastructure/object/s3.go (1)

99-114: 🛠️ Refactor suggestion

Optimize resource management and error handling.

The current implementation has several areas for improvement:

  1. The doneCh is created but not used, making it unnecessary.
  2. Context cancellation is not properly handled.
  3. For buckets with many objects, consider implementing pagination.

Consider this optimized implementation:

 func (s *s3Storer) List(ctx context.Context, prefix string) ([]string, error) {
-    doneCh := make(chan struct{})
-    defer close(doneCh)
-
     var objects []string
     for object := range s.minioClient.ListObjects(ctx, s.bucket, minio.ListObjectsOptions{
         Prefix: prefix,
+        // Optional: Add pagination
+        MaxKeys: 1000,
     }) {
+        // Check for context cancellation
+        select {
+        case <-ctx.Done():
+            return nil, ctx.Err()
+        default:
+        }
+
         if object.Err != nil {
             return nil, fault.Wrap(object.Err, fctx.With(ctx))
         }
         objects = append(objects, object.Key)
     }

     return objects, nil
 }

Let's verify the MinIO client's behavior with context cancellation:

Comment on lines +97 to +105
o := outputs[len(outputs)-1]

if len(outputs) == 0 {
if command == nil {
return nil, fault.New("no output received from module: expected a manifest")
} else {
return nil, fault.New("no output received from module: expected a command response")
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix potential out-of-range panic.
outputs[len(outputs)-1] at line 97 is accessed before verifying that len(outputs) > 0, triggering a panic when no lines are produced by the module.

Below is a suggested fix, checking len(outputs) first:

- o := outputs[len(outputs)-1]
-
 if len(outputs) == 0 {
   if command == nil {
     return nil, fault.New("no output received from module: expected a manifest")
   } else {
     return nil, fault.New("no output received from module: expected a command response")
   }
 }
+o := outputs[len(outputs)-1]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
o := outputs[len(outputs)-1]
if len(outputs) == 0 {
if command == nil {
return nil, fault.New("no output received from module: expected a manifest")
} else {
return nil, fault.New("no output received from module: expected a command response")
}
}
if len(outputs) == 0 {
if command == nil {
return nil, fault.New("no output received from module: expected a manifest")
} else {
return nil, fault.New("no output received from module: expected a command response")
}
}
o := outputs[len(outputs)-1]

Comment on lines +31 to +50
func (w *Writer) Create(ctx context.Context, r io.Reader) error {
b, err := io.ReadAll(r)
if err != nil {
return fault.Wrap(err, fctx.With(ctx))
}

pm, err := plugin.Binary(b).Validate(ctx, w.run)
if err != nil {
return fault.Wrap(err, fctx.With(ctx))
}

p := filepath.Join(plugin.PluginDirectory, pm.Name)

err = w.store.Write(ctx, p, r, int64(len(b)))
if err != nil {
return err
}

return nil
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix potential resource leak and improve error handling.

The code has several issues:

  1. The reader is used twice (ReadAll and Write) but the second use will be empty
  2. Missing cleanup in error cases
  3. Inconsistent error wrapping
 func (w *Writer) Create(ctx context.Context, r io.Reader) error {
 	b, err := io.ReadAll(r)
 	if err != nil {
 		return fault.Wrap(err, fctx.With(ctx))
 	}

 	pm, err := plugin.Binary(b).Validate(ctx, w.run)
 	if err != nil {
 		return fault.Wrap(err, fctx.With(ctx))
 	}

 	p := filepath.Join(plugin.PluginDirectory, pm.Name)
+	// Sanitize the path to prevent directory traversal
+	if !strings.HasPrefix(p, plugin.PluginDirectory) {
+		return fault.New("invalid plugin name")
+	}

-	err = w.store.Write(ctx, p, r, int64(len(b)))
+	err = w.store.Write(ctx, p, bytes.NewReader(b), int64(len(b)))
 	if err != nil {
-		return err
+		return fault.Wrap(err, fctx.With(ctx))
 	}

 	return nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (w *Writer) Create(ctx context.Context, r io.Reader) error {
b, err := io.ReadAll(r)
if err != nil {
return fault.Wrap(err, fctx.With(ctx))
}
pm, err := plugin.Binary(b).Validate(ctx, w.run)
if err != nil {
return fault.Wrap(err, fctx.With(ctx))
}
p := filepath.Join(plugin.PluginDirectory, pm.Name)
err = w.store.Write(ctx, p, r, int64(len(b)))
if err != nil {
return err
}
return nil
}
func (w *Writer) Create(ctx context.Context, r io.Reader) error {
b, err := io.ReadAll(r)
if err != nil {
return fault.Wrap(err, fctx.With(ctx))
}
pm, err := plugin.Binary(b).Validate(ctx, w.run)
if err != nil {
return fault.Wrap(err, fctx.With(ctx))
}
p := filepath.Join(plugin.PluginDirectory, pm.Name)
// Sanitize the path to prevent directory traversal
if !strings.HasPrefix(p, plugin.PluginDirectory) {
return fault.New("invalid plugin name")
}
err = w.store.Write(ctx, p, bytes.NewReader(b), int64(len(b)))
if err != nil {
return fault.Wrap(err, fctx.With(ctx))
}
return nil
}

Comment on lines +38 to +63
ps := dt.Map(files, func(s string) *plugin.Available {
p := path.Join(plugin.PluginDirectory, s)

br, _, err := r.store.Read(ctx, p)
if err != nil {
return &plugin.Available{Error: err}
}

b, err := io.ReadAll(br)
if err != nil {
return &plugin.Available{Error: err}
}

m, err := plugin.Binary(b).Validate(ctx, r.run)
if err != nil {
return &plugin.Available{Error: err}
}

return &plugin.Available{
StoragePath: p,
Loaded: &plugin.Package{
Metadata: *m,
Binary: b,
},
}
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add resource cleanup and improve error handling in Map function.

The current implementation:

  1. Doesn't close the reader
  2. Could benefit from concurrent processing
  3. Missing context cancellation handling
 	ps := dt.Map(files, func(s string) *plugin.Available {
 		p := path.Join(plugin.PluginDirectory, s)

 		br, _, err := r.store.Read(ctx, p)
 		if err != nil {
 			return &plugin.Available{Error: err}
 		}
+		defer br.Close()
+
+		// Check if context is cancelled
+		if ctx.Err() != nil {
+			return &plugin.Available{Error: ctx.Err()}
+		}

 		b, err := io.ReadAll(br)
 		if err != nil {
 			return &plugin.Available{Error: err}
 		}

 		m, err := plugin.Binary(b).Validate(ctx, r.run)
 		if err != nil {
 			return &plugin.Available{Error: err}
 		}

 		return &plugin.Available{
 			StoragePath: p,
 			Loaded: &plugin.Package{
 				Metadata: *m,
 				Binary:   b,
 			},
 		}
 	})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ps := dt.Map(files, func(s string) *plugin.Available {
p := path.Join(plugin.PluginDirectory, s)
br, _, err := r.store.Read(ctx, p)
if err != nil {
return &plugin.Available{Error: err}
}
b, err := io.ReadAll(br)
if err != nil {
return &plugin.Available{Error: err}
}
m, err := plugin.Binary(b).Validate(ctx, r.run)
if err != nil {
return &plugin.Available{Error: err}
}
return &plugin.Available{
StoragePath: p,
Loaded: &plugin.Package{
Metadata: *m,
Binary: b,
},
}
})
ps := dt.Map(files, func(s string) *plugin.Available {
p := path.Join(plugin.PluginDirectory, s)
br, _, err := r.store.Read(ctx, p)
if err != nil {
return &plugin.Available{Error: err}
}
defer br.Close()
// Check if context is cancelled
if ctx.Err() != nil {
return &plugin.Available{Error: ctx.Err()}
}
b, err := io.ReadAll(br)
if err != nil {
return &plugin.Available{Error: err}
}
m, err := plugin.Binary(b).Validate(ctx, r.run)
if err != nil {
return &plugin.Available{Error: err}
}
return &plugin.Available{
StoragePath: p,
Loaded: &plugin.Package{
Metadata: *m,
Binary: b,
},
}
})

Comment on lines +98 to +111
func (s *localStorer) List(ctx context.Context, prefix string) ([]string, error) {
dir := filepath.Join(s.path, prefix)
entries, err := fs.ReadDir(s.s, dir)
if err != nil {
return nil, fault.Wrap(err, fctx.With(ctx))
}

var names []string
for _, entry := range entries {
names = append(names, entry.Name())
}

return names, nil
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider security and performance improvements.

The current implementation has several areas for improvement:

  1. Path traversal vulnerability: Consider sanitizing the prefix parameter to prevent directory traversal attacks.
  2. Memory efficiency: For directories with many entries, consider implementing pagination or streaming.
  3. Entry types: The method doesn't distinguish between files and directories, which might be important for plugin management.

Consider this safer implementation:

 func (s *localStorer) List(ctx context.Context, prefix string) ([]string, error) {
+    // Sanitize the prefix to prevent directory traversal
+    if cleaned := filepath.Clean(prefix); cleaned != prefix {
+        return nil, fault.New("invalid prefix path")
+    }
+
     dir := filepath.Join(s.path, prefix)
     entries, err := fs.ReadDir(s.s, dir)
     if err != nil {
         return nil, fault.Wrap(err, fctx.With(ctx))
     }

     var names []string
     for _, entry := range entries {
+        // Optionally filter by entry type
+        if !entry.IsDir() {
             names = append(names, entry.Name())
+        }
     }

     return names, nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (s *localStorer) List(ctx context.Context, prefix string) ([]string, error) {
dir := filepath.Join(s.path, prefix)
entries, err := fs.ReadDir(s.s, dir)
if err != nil {
return nil, fault.Wrap(err, fctx.With(ctx))
}
var names []string
for _, entry := range entries {
names = append(names, entry.Name())
}
return names, nil
}
func (s *localStorer) List(ctx context.Context, prefix string) ([]string, error) {
// Sanitize the prefix to prevent directory traversal
if cleaned := filepath.Clean(prefix); cleaned != prefix {
return nil, fault.New("invalid prefix path")
}
dir := filepath.Join(s.path, prefix)
entries, err := fs.ReadDir(s.s, dir)
if err != nil {
return nil, fault.Wrap(err, fctx.With(ctx))
}
var names []string
for _, entry := range entries {
// Optionally filter by entry type
if !entry.IsDir() {
names = append(names, entry.Name())
}
}
return names, nil
}

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.

2 participants