Skip to content

Commit 24fdb4c

Browse files
authored
fix(plugins): Harden Akamai and Pipedream config files against line breaks in field values (#677)
1 parent f02c8e0 commit 24fdb4c

4 files changed

Lines changed: 170 additions & 21 deletions

File tree

‎plugins/akamai/api_client_credentials.go‎

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package akamai
22

33
import (
44
"context"
5+
"fmt"
6+
"strings"
57

68
"github.com/1Password/shell-plugins/sdk"
79
"github.com/1Password/shell-plugins/sdk/importer"
@@ -85,25 +87,34 @@ func APIClientCredentials() schema.CredentialType {
8587
}
8688

8789
func configFile(in sdk.ProvisionInput) ([]byte, error) {
88-
contents := "[default]\n"
89-
90-
if clientsecret, ok := in.ItemFields[fieldname.ClientSecret]; ok {
91-
contents += "client_secret = " + clientsecret + "\n"
92-
}
93-
94-
if host, ok := in.ItemFields[fieldname.Host]; ok {
95-
contents += "host = " + host + "\n"
90+
fields := []struct {
91+
name sdk.FieldName
92+
key string
93+
}{
94+
{fieldname.ClientSecret, "client_secret"},
95+
{fieldname.Host, "host"},
96+
{fieldname.AccessToken, "access_token"},
97+
{fieldname.ClientToken, "client_token"},
9698
}
9799

98-
if accesstoken, ok := in.ItemFields[fieldname.AccessToken]; ok {
99-
contents += "access_token = " + accesstoken + "\n"
100-
}
100+
var contents strings.Builder
101+
contents.WriteString("[default]\n")
101102

102-
if clienttoken, ok := in.ItemFields[fieldname.ClientToken]; ok {
103-
contents += "client_token = " + clienttoken + "\n"
103+
for _, field := range fields {
104+
value, ok := in.ItemFields[field.name]
105+
if !ok {
106+
continue
107+
}
108+
// INI parsers ignore surrounding whitespace, so trimming it only drops
109+
// harmless leading or trailing line breaks, such as from a paste.
110+
value = strings.TrimSpace(value)
111+
if strings.ContainsAny(value, "\r\n") {
112+
return nil, fmt.Errorf("line breaks are not allowed in the Akamai %q field", field.name)
113+
}
114+
contents.WriteString(field.key + " = " + value + "\n")
104115
}
105116

106-
return []byte(contents), nil
117+
return []byte(contents.String()), nil
107118
}
108119

109120
// Load credentials from the ~/.edgerc file.

‎plugins/akamai/api_client_credentials_test.go‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package akamai
22

33
import (
4+
"fmt"
45
"testing"
56

67
"github.com/1Password/shell-plugins/sdk"
78
"github.com/1Password/shell-plugins/sdk/plugintest"
89
"github.com/1Password/shell-plugins/sdk/schema/fieldname"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
912
)
1013

1114
func TestAPIClientCredentialsProvisioner(t *testing.T) {
@@ -30,6 +33,66 @@ func TestAPIClientCredentialsProvisioner(t *testing.T) {
3033
})
3134
}
3235

36+
func TestConfigFileRejectsLineBreaks(t *testing.T) {
37+
validFields := map[sdk.FieldName]string{
38+
fieldname.ClientSecret: "abcdE23FNkBxy456z25qx9Yp5CPUxlEfQeTDkfh4QA=I",
39+
fieldname.Host: "akab-lmn789n2k53w7qrs-nfkxaa4lfk3kd6ym.luna.akamaiapis.net",
40+
fieldname.AccessToken: "akab-zyx987xa6osbli4k-e7jf5ikib5jknes3",
41+
fieldname.ClientToken: "akab-nomoflavjuc4422e-fa2xznerxrm3teg7",
42+
}
43+
44+
for field, validValue := range validFields {
45+
for name, lineBreak := range map[string]string{
46+
"line feed": "\n",
47+
"carriage return": "\r",
48+
} {
49+
t.Run(fmt.Sprintf("%s/%s", field, name), func(t *testing.T) {
50+
fields := make(map[sdk.FieldName]string, len(validFields))
51+
for key, value := range validFields {
52+
fields[key] = value
53+
}
54+
fields[field] = validValue + lineBreak + "debug = true"
55+
56+
contents, err := configFile(sdk.ProvisionInput{ItemFields: fields})
57+
58+
assert.Nil(t, contents)
59+
require.EqualError(t, err, fmt.Sprintf("line breaks are not allowed in the Akamai %q field", field))
60+
assert.NotContains(t, err.Error(), "debug = true")
61+
})
62+
}
63+
}
64+
}
65+
66+
func TestConfigFileTrimsSurroundingWhitespace(t *testing.T) {
67+
validFields := map[sdk.FieldName]string{
68+
fieldname.ClientSecret: "abcdE23FNkBxy456z25qx9Yp5CPUxlEfQeTDkfh4QA=I",
69+
fieldname.Host: "akab-lmn789n2k53w7qrs-nfkxaa4lfk3kd6ym.luna.akamaiapis.net",
70+
fieldname.AccessToken: "akab-zyx987xa6osbli4k-e7jf5ikib5jknes3",
71+
fieldname.ClientToken: "akab-nomoflavjuc4422e-fa2xznerxrm3teg7",
72+
}
73+
expected, err := configFile(sdk.ProvisionInput{ItemFields: validFields})
74+
require.NoError(t, err)
75+
76+
for name, pad := range map[string]func(string) string{
77+
"trailing line feed": func(v string) string { return v + "\n" },
78+
"trailing CRLF": func(v string) string { return v + "\r\n" },
79+
"leading line feed": func(v string) string { return "\n" + v },
80+
"surrounding spaces, tabs": func(v string) string { return " \t" + v + "\t " },
81+
} {
82+
t.Run(name, func(t *testing.T) {
83+
fields := make(map[sdk.FieldName]string, len(validFields))
84+
for key, value := range validFields {
85+
fields[key] = pad(value)
86+
}
87+
88+
contents, err := configFile(sdk.ProvisionInput{ItemFields: fields})
89+
90+
require.NoError(t, err)
91+
assert.Equal(t, string(expected), string(contents))
92+
})
93+
}
94+
}
95+
3396
func TestAPIClientCredentialsImporter(t *testing.T) {
3497
plugintest.TestImporter(t, APIClientCredentials().Importer, map[string]plugintest.ImportCase{
3598
"config file with single credential": {

‎plugins/pipedream/api_key.go‎

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package pipedream
22

33
import (
44
"context"
5+
"fmt"
6+
"strings"
57

68
"github.com/1Password/shell-plugins/sdk"
79
"github.com/1Password/shell-plugins/sdk/importer"
@@ -88,15 +90,29 @@ type Config struct {
8890
}
8991

9092
func pipedreamConfig(in sdk.ProvisionInput) ([]byte, error) {
91-
contents := ""
92-
93-
if apikey, ok := in.ItemFields[fieldname.APIKey]; ok {
94-
contents += "api_key = " + apikey + "\n"
93+
fields := []struct {
94+
name sdk.FieldName
95+
key string
96+
}{
97+
{fieldname.APIKey, "api_key"},
98+
{fieldname.OrgID, "org_id"},
9599
}
96100

97-
if orgid, ok := in.ItemFields[fieldname.OrgID]; ok {
98-
contents += "org_id = " + orgid + "\n"
101+
var contents strings.Builder
102+
103+
for _, field := range fields {
104+
value, ok := in.ItemFields[field.name]
105+
if !ok {
106+
continue
107+
}
108+
// INI parsers ignore surrounding whitespace, so trimming it only drops
109+
// harmless leading or trailing line breaks, such as from a paste.
110+
value = strings.TrimSpace(value)
111+
if strings.ContainsAny(value, "\r\n") {
112+
return nil, fmt.Errorf("line breaks are not allowed in the Pipedream %q field", field.name)
113+
}
114+
contents.WriteString(field.key + " = " + value + "\n")
99115
}
100116

101-
return []byte(contents), nil
117+
return []byte(contents.String()), nil
102118
}

‎plugins/pipedream/api_key_test.go‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package pipedream
22

33
import (
4+
"fmt"
45
"testing"
56

67
"github.com/1Password/shell-plugins/sdk"
78
"github.com/1Password/shell-plugins/sdk/plugintest"
89
"github.com/1Password/shell-plugins/sdk/schema/fieldname"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
912
)
1013

1114
func TestAPIKeyProvisioner(t *testing.T) {
@@ -26,6 +29,62 @@ func TestAPIKeyProvisioner(t *testing.T) {
2629
})
2730
}
2831

32+
func TestPipedreamConfigRejectsLineBreaks(t *testing.T) {
33+
validFields := map[sdk.FieldName]string{
34+
fieldname.APIKey: "ugvfxesz62ycsl42z49c0t1hjexample",
35+
fieldname.OrgID: "YbEXAMPLE",
36+
}
37+
38+
for field, validValue := range validFields {
39+
for name, lineBreak := range map[string]string{
40+
"line feed": "\n",
41+
"carriage return": "\r",
42+
} {
43+
t.Run(fmt.Sprintf("%s/%s", field, name), func(t *testing.T) {
44+
fields := make(map[sdk.FieldName]string, len(validFields))
45+
for key, value := range validFields {
46+
fields[key] = value
47+
}
48+
fields[field] = validValue + lineBreak + "[other]"
49+
50+
contents, err := pipedreamConfig(sdk.ProvisionInput{ItemFields: fields})
51+
52+
assert.Nil(t, contents)
53+
require.EqualError(t, err, fmt.Sprintf("line breaks are not allowed in the Pipedream %q field", field))
54+
assert.NotContains(t, err.Error(), "[other]")
55+
})
56+
}
57+
}
58+
}
59+
60+
func TestPipedreamConfigTrimsSurroundingWhitespace(t *testing.T) {
61+
validFields := map[sdk.FieldName]string{
62+
fieldname.APIKey: "ugvfxesz62ycsl42z49c0t1hjexample",
63+
fieldname.OrgID: "YbEXAMPLE",
64+
}
65+
expected, err := pipedreamConfig(sdk.ProvisionInput{ItemFields: validFields})
66+
require.NoError(t, err)
67+
68+
for name, pad := range map[string]func(string) string{
69+
"trailing line feed": func(v string) string { return v + "\n" },
70+
"trailing CRLF": func(v string) string { return v + "\r\n" },
71+
"leading line feed": func(v string) string { return "\n" + v },
72+
"surrounding spaces, tabs": func(v string) string { return " \t" + v + "\t " },
73+
} {
74+
t.Run(name, func(t *testing.T) {
75+
fields := make(map[sdk.FieldName]string, len(validFields))
76+
for key, value := range validFields {
77+
fields[key] = pad(value)
78+
}
79+
80+
contents, err := pipedreamConfig(sdk.ProvisionInput{ItemFields: fields})
81+
82+
require.NoError(t, err)
83+
assert.Equal(t, string(expected), string(contents))
84+
})
85+
}
86+
}
87+
2988
func TestAPIKeyImporter(t *testing.T) {
3089
plugintest.TestImporter(t, APIKey().Importer, map[string]plugintest.ImportCase{
3190
"config file": {

0 commit comments

Comments
 (0)