Secrets Store General Availability

We won’t be able to implement this in a secure way. As soon as the secret gets onto developer computer, there is no way for us to ensure it is not captured and saved somewhere.

I found and fixed the problem in Studio. Next release will have the fix.

2 Likes

Is it currently not possible to have two API keys in one URL?

API I’m using requires ?key={key0}&token={key1} but using two secrets doesnt seem to work…

Right now I need to combine the first part of the URL (a string) and then the two queries which cannot be concatenated with Secrets. The basic AddPrefix and AddSuffix seem to be only for singular queries even though many APIs require multiple.

Solution:
If you must append multiple secrets to a URL like you are saying you can save the entire portion of the URL as a single secret. Instead of saving key0 and key1 as individual secrets it would be something like setting a secret as: ?key={your-key-here}&token={your-token-here} for the domain you want to use. Not the cleanest solution, but it should fix your problem.

Security Concern:
If possible you should really be passing these arguments as headers rather than as part of the URL. While I don’t think you will have any security problems with Roblox’s servers making these calls, it’s best practice to do this and is good to build as a habit. If the API you are using doesnt support that then ¯\(ツ)/¯ but if it’s your own API I suggest implementing authentication checks via headers and not just URL queries.

I’d like to see compatibility with being able to pass both secrets and stringswith AddSuffix and AddPrefix, but currently we can only pass strings as an argument for these methods. Having multiple keys as a query rather than passed headers is something that a lot of legacy APIs use and it could be nice to have some support for the older web.

2 Likes

Hey,

I’m pretty sure a new Studio version has just rolled out. Secrets still don’t save and persist between sessions. I haven’t yet tested whether they actually work locally.

Can you confirm the fix for both of these issues has been released?

Release notes for version 630 haven’t been released yet, so I’m guessing we are still in version 629 even tho it’s around the time for an update. However, you should note that many updates for version 629 are still pending and not live, so even if the update has been pushed it could still be pending approval.

1 Like

Might not even appear on the release notes. Some bug fixes don’t. But we’ll see :man_shrugging:

Locally-saved secrets still do not save across sessions or work at all in playtesting. It seems nothing has been fixed here. Have confirmed on version 630.

Could be the case that it hasn’t went LIVE yet.

Indeed, this is not supported. It sounds like a useful feature, allow AddPrefix/AddSuffix concatenate another secret, but here I agree with Sammy, passing multiple secrets via URL feels unnatural.

Yes, this is our usual practice, to employ “slow rollout”. We must ensure that changes do not break anything else in the system, hence the need for such practice. It is at 100% now, and should be working for everyone.

1 Like

Thanks for the info. Secrets now save between Studio sessions but I still can’t seem to access them via scripts.

It likely means there is some mismatch in the JSON or script. For example, secret name, or JSON format, or secret content not being base64-encoded. If you could paste the JSON and a script, that might be helpful. We don’t validate JSON when saving it (even invalid input will be saved, but it won’t be loaded in the game server, causing “Secret not found”).

1 Like

I do agree that passing the secrets as parameters is definitely unsecure. But it’s just how the Trello API (developed by Atlassian) works. Feature support for this would be very nice but I guess for now I will have to use the solution @Mmm_Wafflez came up with.

Hey again,

Not sure what happened but it seems to be working now! Thanks a lot for your continued assistance.

I’ll be sure to let you know if I experience any further issues.

I’d recommend adding a regex check to validate JSON when saving as this could be a somewhat annoying thing to debug with a pretty easy fix. Low priority ticket item tho.

Heres some examples of how to do this:
Perl and JS:

Regex strings are pretty much the same across different languages so I hope you can implement this whenever you have spare time.

2 Likes

How do i add a secret to this window?

I am unsure of the format and is unable to find documentation

come on man, I can’t even use it

You want to write Secrets in JSON format wrapped with curly brackets where the key is the Secret name and the value is an array with the first value as the Token and the second value as the whitelisted URL. Note that you should prepend URLs with a * (wildcard) to make sure that all subdomains associated with a domain are whitelisted aswell.

2 Likes

Roblox has been down just before and it caused the method GetSecret to fail (telling the secret was not found), which my game was not designed to handle at all and broke everything because I didn’t even know the method could error in this case. It would be nice to either document that this function may error in case roblox gets down (so advise to wrap in a pcall ), or either return a secret but with empty value or whatever.

2 Likes

edit 2: When i run local Test, i’m able to retrieve the secret, but when i try to use it in a request such as:

local response = HttpService:PostAsync(
	apiUrl, 
	HttpService:JSONEncode(self.payload),
	Enum.HttpContentType.ApplicationJson,
	false,
	{["api-key"] = apiKey} -- secret
)

I get the error:

  Header "api-key" has unallowed character  -  Server 

However, team test is working with the api-key i have put the web portal. therefore im assuming there is some issue with the formatting in Game Settings > Security > Secrets. how can we verify that it is being parsed correctly?


edit 1: i’m guessing it doesn’t work in command bar, it seems to work locally - however new issue: there is no documentation on how to use Secrets with PostAsync header variant or RequestAsync headers. when i try to use it i get “header must be a dictionary”. this feature is borderline useless.


command bar doesn’t appear to be working. maybe i am doing something wrong.

Sorry, I missed this message. The locally defined secret should be base64 encoded. So, the Secrets box should be: `{“test”: [“Zm9vYmFy”, “*”]}
I am making changes to documentation to provide a useful example.

ok thanks! ill test it out and see. im hoping at some point maybe we could just get like a local env file that we can import and the local secrets engine will handle it. right now i have been having it fail gracefully on local and testing normally in team test which has not been a bad experience.

Using OpenCloud API

Secrets can be managed programmatically, via an OpenCloud API. You need Secrets Store API System enabled for your API Key. Alternatively, you may create an OAuth App with universe.secret:read and universe.secret:write permissions. Since secrets are associated with Experiences, you need to know the universeID.

List Experience secrets

Lists all secrets defined for this experience. Secret content is not returned. Requires read permission.

Example:

curl -H 'Content-Type: application/json'\
    'https://apis.roblox.com/cloud/v2/universes/<universeID>/secrets'\
    -H 'x-api-key: <API Key>'

Sample Response:

{"secrets":
    [{
        "id":"gcp",
        "domain":"*.google.com",
        "created":"2023-11-07T18:15:01.809Z",
        "updated":"2023-11-07T18:15:01.809Z"
    }],
    "nextPageCursor":"n2o2djcA",
    "previousPageCursor":"p2o2djcA"
}

Get Experience public key

Retrieves the public key for your experience. You need it to encrypt the secret content before sending it to Roblox. The API requires read permission.

Example:

curl -H 'Content-Type: application/json'\
    'https://apis.roblox.com/cloud/v2/universes/<universeID>/secrets/public-key'\
    -H 'x-api-key: <API Key>'

Sample Response:

{
    "id":"public-key",
    "secret":"Zgj4+V7vSaEZ06rXazKJUIcUnVa95tUNiwXAif/vdHo=",
    "key_id":"1200590785272263122"
}

Create a new Secret

Creates a new Secret (fails if a secret with this name already exists), which requires write permission. The secret must be encrypted before sending it to Roblox. Encrypt your secret using LibSodium sealed box. The example below uses Python and PyNaCl to create a sealed box (run pip install pynacl to install it locally).

Create Python script with this content (use your own public key and secret content there):

from base64 import b64encode
from nacl import encoding, public

public_key = "Zgj4+V7vSaEZ06rXazKJUIcUnVa95tUNiwXAif/vdHo="
secret_content = "my_api_key_content"
public_key = public.PublicKey(public_key.encode("utf-8"), encoding.Base64Encoder())
sealed_box = public.SealedBox(public_key)
encrypted = sealed_box.encrypt(secret_content.encode("utf-8"))
print(b64encode(encrypted).decode("utf-8"))

Run the script python3 seal_box.py. Script output is your encrypted secret, e.g. Bgy1ky7Se3xSquRc1b/T1C1FIxXbEDppMRA2trpJy1luT7NY6UQDT+rk6qnnGq09lQl9EQQU5xKsWCk=

Example creating a new secret and sending encrypted content:

curl -H 'Content-Type: application/json'\ 'https://apis.roblox.com/cloud/v2/universes/<universeID>/secrets'\
    -d '{"id":"gcp","domain":"*.google.com",
"secret":"Bgy1ky7Se3xSquRc1b/T1C1FIxXbEDppMRA2trpJy1luT7NY6UQDT+rk6qnnGq09lQl9EQQU5xKsWCk=", "key_id":"1200590785272263122"}'\
    -H 'x-api-key: <API Key>'

Sample Response:

{
    "id":"gcp",
    "created":"2023-11-07T18:15:01.809Z",
    "updated":"2023-11-07T18:15:01.809Z"
}

Update existing Secret

Use the same encryption script to get the sealed box. Requires write permission.

Example:

curl -X PATCH -H 'Content-Type: application/json'\
    'https://apis.roblox.com/cloud/v2/universes/<universeID>/secrets/gcp'\
    -d '{"secret":"rlkpVPeg+Fyh+eSTGLTySlQBV8MszuOcvq56SGWHLU39avqLw24bDmkhQHms+6NOlnJ6kiPbCG1KE70=", "key_id":"1200590785272263122"}'\
    -H 'x-api-key: <API Key>'

Sample Response:

{"updated":"2023-11-07T18:23:53.334Z"}

Delete existing Secret

Requires write permission.

Example:

curl -X DELETE -H 'Content-Type: application/json'\
    'https://apis.roblox.com/cloud/v2/universes/<universeID>/secrets/gcp'\
    -H 'x-api-key: <API Key>'

Response:

HTTP status code 200 when successful.

1 Like