Skip to content

credential provider follow-ups: name collisions, vendor change, and an untested secret-delete risk #2093

Description

@notgitika

Status

The drop bug is fixed by #2089, which creates credential providers in CloudFormation. Two of the six open questions below are answered; four are still open. This issue now tracks those four.

The original bug

On the refactor branch, agentcore project deploy silently ignored any credential declared in agentcore.json — the provider was never created and the deploy reported success:

$ agentcore project add credentials api-key --name openai-key
$ agentcore project deploy
...
Deployed project 'Example' to target 'default' # exit 0
$ agentcore identity api-key-credential-provider get --name openai-key
Error: ApiKeyCredentialProvider not found for openai-key

The vended CDK app expected provider ARNs to already be recorded in agentcore/.cli/deployed-state.json (src/assets/cdk/bin/cdk.ts, read inside a try/catch that tolerated absence). Nothing in the deploy path wrote that file, so the credentials map was always undefined and every declaration fell on the floor — silently, because the read tolerated a missing file.

Not in main; refactor only.

How #2089 fixes it

src/assets/cdk/lib/cdk-stack.ts creates a provider for each declared credential using the constructs from L3 #333. Payment connectors now carry the credential's name and the stack resolves it to the ARN of the provider it created, which removed the last reader of deployed-state.json.

Answered: .env.local secrets are neither plaintext nor EXTERNAL

The original framing offered two options and both were bad. There is a third, which is what L3 #333 built for: the provider is created with CREDENTIAL_SECRET_PLACEHOLDER, and the CLI replaces it over the Identity API once the deploy succeeds. Real secret material never enters the template. A credential with a secretRef/clientSecretRef deploys as EXTERNAL and needs no sync.

One correction to the L3's own docstring, which suggests detecting an unsynced provider by finding the placeholder still in place: that is not implementable. ApiKey is a CloudFormation write-only property and GetApiKeyCredentialProvider returns only apiKeySecretArn — there is nothing to read back and compare. #2089 therefore syncs unconditionally on every deploy, at the cost of a new secret version each time.

Answered: secret rotation

Moot under the placeholder design. The template never carries the real key, so a stack update cannot overwrite a rotated secret with a stale one. If CloudFormation does update the resource for some other reason, it writes the placeholder and the post-deploy sync immediately replaces it.

Still open

1. Provider names are not scoped to a project, so stacks collide

AgentCoreApiKeyCredentialProvider sets the CFN Name to the bare credential name (AgentCoreCredentialProvider.ts:47, :116) — projectName only feeds tags. Names are unique per (account, region, token vault), so:

  • two projects in one account+region both declaring openai-key collide
  • two targets of one project in the same region collide — each target gets its own stack, and both try to create the same provider name

The imperative prototype reused an existing provider, so this used to be silent sharing; under CloudFormation the second stack fails with AlreadyExists. A hard failure is the better default, but it is a behaviour change and it makes a same-region two-target project undeployable.

Needs either a naming strategy (project- or target-scoped names, which changes what users pass to agentcore identity ...) or CFN resource import for adoption. The L3's ExternallyManagedStateSchema covers only customJwtAuthorizer and vpcConfig, so there is no existing escape hatch.

2. Changing a vendor is a replacement into a name collision

CredentialProviderVendor is create-only on OAuth2 and Payment, and Name is create-only too. Changing a credential's vendor while keeping its name makes CloudFormation replace the resource with one claiming a name still held by the resource being replaced. Needs a guard that refuses the edit with an actionable message.

3. Unverified: does deleting a stack destroy a customer-owned secret?

The delete handler lists secretsmanager:DeleteSecret unconditionally. If that applies to an EXTERNAL secret, deleting a stack destroys a secret the customer owns and manages elsewhere. Still untested.

This is now cheap to test: deploy a project with a secretRef credential, delete the stack, and check whether the referenced secret survives. Worth doing before refactor ships — it is the highest-severity unknown left here.

4. CredentialNameSchema.min(3)

Still a workaround for the pinned L3 rejecting shorter names at build (src/projectSchemas/credential.ts). Since we maintain the L3, align it and drop back to 1.

Moved out

Payment credential providers are tracked in #2095. project deploy refuses them with an explicit error rather than half-creating one; that issue covers both the Quick Create path (needs no provider, already released in L3 alpha.49 via L3 #324) and the Manual path's schema gap.

Reference, still accurate

  • CloudFormation support: ApiKeyCredentialProvider, OAuth2CredentialProvider and PaymentCredentialProvider are all LIVE and FULLY_MUTABLE in the us-east-1 registry. On AWS::BedrockAgentCore::ApiKeyCredentialProvider: readOnlyProperties include CredentialProviderArn and ApiKeySecretArn; writeOnlyProperties are ApiKey, ApiKeySecretConfig, ApiKeySecretSource; createOnlyProperties is Name.
  • The L3 tolerates a CDK token for a credential ARN. CredentialDeployedStateSchema.credentialProviderArn is a bare z.string(), and all consumption sites pass it straight into a CFN property or a truthiness check — no .split/.match/.slice/.startsWith/.replace on a credential ARN anywhere. IAM grants are built from the credential name plus partition/region/account, never by parsing the ARN. So no L3 schema change was needed.
  • OAuth2CredentialProvider differs in shape: ClientSecretSource is read-only at the top level, so the secret config lives insideOauth2ProviderConfigInput. All 9 vendor configs support ClientSecretConfig + ClientSecretSource.
  • CustomOauth2ProviderConfigInput requires OauthDiscovery and has no Scopes field, which is why a spec's scopes are consumed where the credential is used, not where the provider is made.

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

    , 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
     blocks
    (function() {
    function addCopyButtons() {
    document.querySelectorAll('pre code').forEach(function(codeBlock) {
    if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
    codeBlock.parentElement.setAttribute('data-copy-added', 'true');
    var btn = document.createElement('button');
    btn.textContent = 'Copy';
    btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
    btn.onmouseover = function() { this.style.opacity = '1'; };
    btn.onmouseout = function() { this.style.opacity = '0.7'; };
    btn.onclick = function() {
    navigator.clipboard.writeText(codeBlock.textContent).then(function() {
    btn.textContent = 'Copied!';
    setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
    });
    };
    codeBlock.parentElement.style.position = 'relative';
    codeBlock.parentElement.appendChild(btn);
    });
    }
    addCopyButtons();
    // Re-run on dynamic content
    var observer = new MutationObserver(addCopyButtons);
    observer.observe(document.body, { childList: true, subtree: true });
    })();
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    credential provider follow-ups: name collisions, vendor change, and an untested secret-delete risk · Issue #2093 · aws/agentcore-cli · GitHub
    Skip to content

    credential provider follow-ups: name collisions, vendor change, and an untested secret-delete risk #2093

    Description

    @notgitika

    Status

    The drop bug is fixed by #2089, which creates credential providers in CloudFormation. Two of the six open questions below are answered; four are still open. This issue now tracks those four.

    The original bug

    On the refactor branch, agentcore project deploy silently ignored any credential declared in agentcore.json — the provider was never created and the deploy reported success:

    $ agentcore project add credentials api-key --name openai-key
    $ agentcore project deploy
    ...
    Deployed project 'Example' to target 'default' # exit 0
    $ agentcore identity api-key-credential-provider get --name openai-key
    Error: ApiKeyCredentialProvider not found for openai-key
    

    The vended CDK app expected provider ARNs to already be recorded in agentcore/.cli/deployed-state.json (src/assets/cdk/bin/cdk.ts, read inside a try/catch that tolerated absence). Nothing in the deploy path wrote that file, so the credentials map was always undefined and every declaration fell on the floor — silently, because the read tolerated a missing file.

    Not in main; refactor only.

    How #2089 fixes it

    src/assets/cdk/lib/cdk-stack.ts creates a provider for each declared credential using the constructs from L3 #333. Payment connectors now carry the credential's name and the stack resolves it to the ARN of the provider it created, which removed the last reader of deployed-state.json.

    Answered: .env.local secrets are neither plaintext nor EXTERNAL

    The original framing offered two options and both were bad. There is a third, which is what L3 #333 built for: the provider is created with CREDENTIAL_SECRET_PLACEHOLDER, and the CLI replaces it over the Identity API once the deploy succeeds. Real secret material never enters the template. A credential with a secretRef/clientSecretRef deploys as EXTERNAL and needs no sync.

    One correction to the L3's own docstring, which suggests detecting an unsynced provider by finding the placeholder still in place: that is not implementable. ApiKey is a CloudFormation write-only property and GetApiKeyCredentialProvider returns only apiKeySecretArn — there is nothing to read back and compare. #2089 therefore syncs unconditionally on every deploy, at the cost of a new secret version each time.

    Answered: secret rotation

    Moot under the placeholder design. The template never carries the real key, so a stack update cannot overwrite a rotated secret with a stale one. If CloudFormation does update the resource for some other reason, it writes the placeholder and the post-deploy sync immediately replaces it.

    Still open

    1. Provider names are not scoped to a project, so stacks collide

    AgentCoreApiKeyCredentialProvider sets the CFN Name to the bare credential name (AgentCoreCredentialProvider.ts:47, :116) — projectName only feeds tags. Names are unique per (account, region, token vault), so:

    • two projects in one account+region both declaring openai-key collide
    • two targets of one project in the same region collide — each target gets its own stack, and both try to create the same provider name

    The imperative prototype reused an existing provider, so this used to be silent sharing; under CloudFormation the second stack fails with AlreadyExists. A hard failure is the better default, but it is a behaviour change and it makes a same-region two-target project undeployable.

    Needs either a naming strategy (project- or target-scoped names, which changes what users pass to agentcore identity ...) or CFN resource import for adoption. The L3's ExternallyManagedStateSchema covers only customJwtAuthorizer and vpcConfig, so there is no existing escape hatch.

    2. Changing a vendor is a replacement into a name collision

    CredentialProviderVendor is create-only on OAuth2 and Payment, and Name is create-only too. Changing a credential's vendor while keeping its name makes CloudFormation replace the resource with one claiming a name still held by the resource being replaced. Needs a guard that refuses the edit with an actionable message.

    3. Unverified: does deleting a stack destroy a customer-owned secret?

    The delete handler lists secretsmanager:DeleteSecret unconditionally. If that applies to an EXTERNAL secret, deleting a stack destroys a secret the customer owns and manages elsewhere. Still untested.

    This is now cheap to test: deploy a project with a secretRef credential, delete the stack, and check whether the referenced secret survives. Worth doing before refactor ships — it is the highest-severity unknown left here.

    4. CredentialNameSchema.min(3)

    Still a workaround for the pinned L3 rejecting shorter names at build (src/projectSchemas/credential.ts). Since we maintain the L3, align it and drop back to 1.

    Moved out

    Payment credential providers are tracked in #2095. project deploy refuses them with an explicit error rather than half-creating one; that issue covers both the Quick Create path (needs no provider, already released in L3 alpha.49 via L3 #324) and the Manual path's schema gap.

    Reference, still accurate

    • CloudFormation support: ApiKeyCredentialProvider, OAuth2CredentialProvider and PaymentCredentialProvider are all LIVE and FULLY_MUTABLE in the us-east-1 registry. On AWS::BedrockAgentCore::ApiKeyCredentialProvider: readOnlyProperties include CredentialProviderArn and ApiKeySecretArn; writeOnlyProperties are ApiKey, ApiKeySecretConfig, ApiKeySecretSource; createOnlyProperties is Name.
    • The L3 tolerates a CDK token for a credential ARN. CredentialDeployedStateSchema.credentialProviderArn is a bare z.string(), and all consumption sites pass it straight into a CFN property or a truthiness check — no .split/.match/.slice/.startsWith/.replace on a credential ARN anywhere. IAM grants are built from the credential name plus partition/region/account, never by parsing the ARN. So no L3 schema change was needed.
    • OAuth2CredentialProvider differs in shape: ClientSecretSource is read-only at the top level, so the secret config lives insideOauth2ProviderConfigInput. All 9 vendor configs support ClientSecretConfig + ClientSecretSource.
    • CustomOauth2ProviderConfigInput requires OauthDiscovery and has no Scopes field, which is why a spec's scopes are consumed where the credential is used, not where the provider is made.

    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

      , 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' credential provider follow-ups: name collisions, vendor change, and an untested secret-delete risk · Issue #2093 · aws/agentcore-cli · GitHub
      Skip to content

      credential provider follow-ups: name collisions, vendor change, and an untested secret-delete risk #2093

      Description

      @notgitika

      Status

      The drop bug is fixed by #2089, which creates credential providers in CloudFormation. Two of the six open questions below are answered; four are still open. This issue now tracks those four.

      The original bug

      On the refactor branch, agentcore project deploy silently ignored any credential declared in agentcore.json — the provider was never created and the deploy reported success:

      $ agentcore project add credentials api-key --name openai-key
      $ agentcore project deploy
      ...
      Deployed project 'Example' to target 'default' # exit 0
      $ agentcore identity api-key-credential-provider get --name openai-key
      Error: ApiKeyCredentialProvider not found for openai-key
      

      The vended CDK app expected provider ARNs to already be recorded in agentcore/.cli/deployed-state.json (src/assets/cdk/bin/cdk.ts, read inside a try/catch that tolerated absence). Nothing in the deploy path wrote that file, so the credentials map was always undefined and every declaration fell on the floor — silently, because the read tolerated a missing file.

      Not in main; refactor only.

      How #2089 fixes it

      src/assets/cdk/lib/cdk-stack.ts creates a provider for each declared credential using the constructs from L3 #333. Payment connectors now carry the credential's name and the stack resolves it to the ARN of the provider it created, which removed the last reader of deployed-state.json.

      Answered: .env.local secrets are neither plaintext nor EXTERNAL

      The original framing offered two options and both were bad. There is a third, which is what L3 #333 built for: the provider is created with CREDENTIAL_SECRET_PLACEHOLDER, and the CLI replaces it over the Identity API once the deploy succeeds. Real secret material never enters the template. A credential with a secretRef/clientSecretRef deploys as EXTERNAL and needs no sync.

      One correction to the L3's own docstring, which suggests detecting an unsynced provider by finding the placeholder still in place: that is not implementable. ApiKey is a CloudFormation write-only property and GetApiKeyCredentialProvider returns only apiKeySecretArn — there is nothing to read back and compare. #2089 therefore syncs unconditionally on every deploy, at the cost of a new secret version each time.

      Answered: secret rotation

      Moot under the placeholder design. The template never carries the real key, so a stack update cannot overwrite a rotated secret with a stale one. If CloudFormation does update the resource for some other reason, it writes the placeholder and the post-deploy sync immediately replaces it.

      Still open

      1. Provider names are not scoped to a project, so stacks collide

      AgentCoreApiKeyCredentialProvider sets the CFN Name to the bare credential name (AgentCoreCredentialProvider.ts:47, :116) — projectName only feeds tags. Names are unique per (account, region, token vault), so:

      • two projects in one account+region both declaring openai-key collide
      • two targets of one project in the same region collide — each target gets its own stack, and both try to create the same provider name

      The imperative prototype reused an existing provider, so this used to be silent sharing; under CloudFormation the second stack fails with AlreadyExists. A hard failure is the better default, but it is a behaviour change and it makes a same-region two-target project undeployable.

      Needs either a naming strategy (project- or target-scoped names, which changes what users pass to agentcore identity ...) or CFN resource import for adoption. The L3's ExternallyManagedStateSchema covers only customJwtAuthorizer and vpcConfig, so there is no existing escape hatch.

      2. Changing a vendor is a replacement into a name collision

      CredentialProviderVendor is create-only on OAuth2 and Payment, and Name is create-only too. Changing a credential's vendor while keeping its name makes CloudFormation replace the resource with one claiming a name still held by the resource being replaced. Needs a guard that refuses the edit with an actionable message.

      3. Unverified: does deleting a stack destroy a customer-owned secret?

      The delete handler lists secretsmanager:DeleteSecret unconditionally. If that applies to an EXTERNAL secret, deleting a stack destroys a secret the customer owns and manages elsewhere. Still untested.

      This is now cheap to test: deploy a project with a secretRef credential, delete the stack, and check whether the referenced secret survives. Worth doing before refactor ships — it is the highest-severity unknown left here.

      4. CredentialNameSchema.min(3)

      Still a workaround for the pinned L3 rejecting shorter names at build (src/projectSchemas/credential.ts). Since we maintain the L3, align it and drop back to 1.

      Moved out

      Payment credential providers are tracked in #2095. project deploy refuses them with an explicit error rather than half-creating one; that issue covers both the Quick Create path (needs no provider, already released in L3 alpha.49 via L3 #324) and the Manual path's schema gap.

      Reference, still accurate

      • CloudFormation support: ApiKeyCredentialProvider, OAuth2CredentialProvider and PaymentCredentialProvider are all LIVE and FULLY_MUTABLE in the us-east-1 registry. On AWS::BedrockAgentCore::ApiKeyCredentialProvider: readOnlyProperties include CredentialProviderArn and ApiKeySecretArn; writeOnlyProperties are ApiKey, ApiKeySecretConfig, ApiKeySecretSource; createOnlyProperties is Name.
      • The L3 tolerates a CDK token for a credential ARN. CredentialDeployedStateSchema.credentialProviderArn is a bare z.string(), and all consumption sites pass it straight into a CFN property or a truthiness check — no .split/.match/.slice/.startsWith/.replace on a credential ARN anywhere. IAM grants are built from the credential name plus partition/region/account, never by parsing the ARN. So no L3 schema change was needed.
      • OAuth2CredentialProvider differs in shape: ClientSecretSource is read-only at the top level, so the secret config lives insideOauth2ProviderConfigInput. All 9 vendor configs support ClientSecretConfig + ClientSecretSource.
      • CustomOauth2ProviderConfigInput requires OauthDiscovery and has no Scopes field, which is why a spec's scopes are consumed where the credential is used, not where the provider is made.

      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

        , 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' credential provider follow-ups: name collisions, vendor change, and an untested secret-delete risk · Issue #2093 · aws/agentcore-cli · GitHub
        Skip to content

        credential provider follow-ups: name collisions, vendor change, and an untested secret-delete risk #2093

        Description

        @notgitika

        Status

        The drop bug is fixed by #2089, which creates credential providers in CloudFormation. Two of the six open questions below are answered; four are still open. This issue now tracks those four.

        The original bug

        On the refactor branch, agentcore project deploy silently ignored any credential declared in agentcore.json — the provider was never created and the deploy reported success:

        $ agentcore project add credentials api-key --name openai-key
        $ agentcore project deploy
        ...
        Deployed project 'Example' to target 'default' # exit 0
        $ agentcore identity api-key-credential-provider get --name openai-key
        Error: ApiKeyCredentialProvider not found for openai-key
        

        The vended CDK app expected provider ARNs to already be recorded in agentcore/.cli/deployed-state.json (src/assets/cdk/bin/cdk.ts, read inside a try/catch that tolerated absence). Nothing in the deploy path wrote that file, so the credentials map was always undefined and every declaration fell on the floor — silently, because the read tolerated a missing file.

        Not in main; refactor only.

        How #2089 fixes it

        src/assets/cdk/lib/cdk-stack.ts creates a provider for each declared credential using the constructs from L3 #333. Payment connectors now carry the credential's name and the stack resolves it to the ARN of the provider it created, which removed the last reader of deployed-state.json.

        Answered: .env.local secrets are neither plaintext nor EXTERNAL

        The original framing offered two options and both were bad. There is a third, which is what L3 #333 built for: the provider is created with CREDENTIAL_SECRET_PLACEHOLDER, and the CLI replaces it over the Identity API once the deploy succeeds. Real secret material never enters the template. A credential with a secretRef/clientSecretRef deploys as EXTERNAL and needs no sync.

        One correction to the L3's own docstring, which suggests detecting an unsynced provider by finding the placeholder still in place: that is not implementable. ApiKey is a CloudFormation write-only property and GetApiKeyCredentialProvider returns only apiKeySecretArn — there is nothing to read back and compare. #2089 therefore syncs unconditionally on every deploy, at the cost of a new secret version each time.

        Answered: secret rotation

        Moot under the placeholder design. The template never carries the real key, so a stack update cannot overwrite a rotated secret with a stale one. If CloudFormation does update the resource for some other reason, it writes the placeholder and the post-deploy sync immediately replaces it.

        Still open

        1. Provider names are not scoped to a project, so stacks collide

        AgentCoreApiKeyCredentialProvider sets the CFN Name to the bare credential name (AgentCoreCredentialProvider.ts:47, :116) — projectName only feeds tags. Names are unique per (account, region, token vault), so:

        • two projects in one account+region both declaring openai-key collide
        • two targets of one project in the same region collide — each target gets its own stack, and both try to create the same provider name

        The imperative prototype reused an existing provider, so this used to be silent sharing; under CloudFormation the second stack fails with AlreadyExists. A hard failure is the better default, but it is a behaviour change and it makes a same-region two-target project undeployable.

        Needs either a naming strategy (project- or target-scoped names, which changes what users pass to agentcore identity ...) or CFN resource import for adoption. The L3's ExternallyManagedStateSchema covers only customJwtAuthorizer and vpcConfig, so there is no existing escape hatch.

        2. Changing a vendor is a replacement into a name collision

        CredentialProviderVendor is create-only on OAuth2 and Payment, and Name is create-only too. Changing a credential's vendor while keeping its name makes CloudFormation replace the resource with one claiming a name still held by the resource being replaced. Needs a guard that refuses the edit with an actionable message.

        3. Unverified: does deleting a stack destroy a customer-owned secret?

        The delete handler lists secretsmanager:DeleteSecret unconditionally. If that applies to an EXTERNAL secret, deleting a stack destroys a secret the customer owns and manages elsewhere. Still untested.

        This is now cheap to test: deploy a project with a secretRef credential, delete the stack, and check whether the referenced secret survives. Worth doing before refactor ships — it is the highest-severity unknown left here.

        4. CredentialNameSchema.min(3)

        Still a workaround for the pinned L3 rejecting shorter names at build (src/projectSchemas/credential.ts). Since we maintain the L3, align it and drop back to 1.

        Moved out

        Payment credential providers are tracked in #2095. project deploy refuses them with an explicit error rather than half-creating one; that issue covers both the Quick Create path (needs no provider, already released in L3 alpha.49 via L3 #324) and the Manual path's schema gap.

        Reference, still accurate

        • CloudFormation support: ApiKeyCredentialProvider, OAuth2CredentialProvider and PaymentCredentialProvider are all LIVE and FULLY_MUTABLE in the us-east-1 registry. On AWS::BedrockAgentCore::ApiKeyCredentialProvider: readOnlyProperties include CredentialProviderArn and ApiKeySecretArn; writeOnlyProperties are ApiKey, ApiKeySecretConfig, ApiKeySecretSource; createOnlyProperties is Name.
        • The L3 tolerates a CDK token for a credential ARN. CredentialDeployedStateSchema.credentialProviderArn is a bare z.string(), and all consumption sites pass it straight into a CFN property or a truthiness check — no .split/.match/.slice/.startsWith/.replace on a credential ARN anywhere. IAM grants are built from the credential name plus partition/region/account, never by parsing the ARN. So no L3 schema change was needed.
        • OAuth2CredentialProvider differs in shape: ClientSecretSource is read-only at the top level, so the secret config lives insideOauth2ProviderConfigInput. All 9 vendor configs support ClientSecretConfig + ClientSecretSource.
        • CustomOauth2ProviderConfigInput requires OauthDiscovery and has no Scopes field, which is why a spec's scopes are consumed where the credential is used, not where the provider is made.

        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

          , 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' credential provider follow-ups: name collisions, vendor change, and an untested secret-delete risk · Issue #2093 · aws/agentcore-cli · GitHub
          Skip to content

          credential provider follow-ups: name collisions, vendor change, and an untested secret-delete risk #2093

          Description

          @notgitika

          Status

          The drop bug is fixed by #2089, which creates credential providers in CloudFormation. Two of the six open questions below are answered; four are still open. This issue now tracks those four.

          The original bug

          On the refactor branch, agentcore project deploy silently ignored any credential declared in agentcore.json — the provider was never created and the deploy reported success:

          $ agentcore project add credentials api-key --name openai-key
          $ agentcore project deploy
          ...
          Deployed project 'Example' to target 'default' # exit 0
          $ agentcore identity api-key-credential-provider get --name openai-key
          Error: ApiKeyCredentialProvider not found for openai-key
          

          The vended CDK app expected provider ARNs to already be recorded in agentcore/.cli/deployed-state.json (src/assets/cdk/bin/cdk.ts, read inside a try/catch that tolerated absence). Nothing in the deploy path wrote that file, so the credentials map was always undefined and every declaration fell on the floor — silently, because the read tolerated a missing file.

          Not in main; refactor only.

          How #2089 fixes it

          src/assets/cdk/lib/cdk-stack.ts creates a provider for each declared credential using the constructs from L3 #333. Payment connectors now carry the credential's name and the stack resolves it to the ARN of the provider it created, which removed the last reader of deployed-state.json.

          Answered: .env.local secrets are neither plaintext nor EXTERNAL

          The original framing offered two options and both were bad. There is a third, which is what L3 #333 built for: the provider is created with CREDENTIAL_SECRET_PLACEHOLDER, and the CLI replaces it over the Identity API once the deploy succeeds. Real secret material never enters the template. A credential with a secretRef/clientSecretRef deploys as EXTERNAL and needs no sync.

          One correction to the L3's own docstring, which suggests detecting an unsynced provider by finding the placeholder still in place: that is not implementable. ApiKey is a CloudFormation write-only property and GetApiKeyCredentialProvider returns only apiKeySecretArn — there is nothing to read back and compare. #2089 therefore syncs unconditionally on every deploy, at the cost of a new secret version each time.

          Answered: secret rotation

          Moot under the placeholder design. The template never carries the real key, so a stack update cannot overwrite a rotated secret with a stale one. If CloudFormation does update the resource for some other reason, it writes the placeholder and the post-deploy sync immediately replaces it.

          Still open

          1. Provider names are not scoped to a project, so stacks collide

          AgentCoreApiKeyCredentialProvider sets the CFN Name to the bare credential name (AgentCoreCredentialProvider.ts:47, :116) — projectName only feeds tags. Names are unique per (account, region, token vault), so:

          • two projects in one account+region both declaring openai-key collide
          • two targets of one project in the same region collide — each target gets its own stack, and both try to create the same provider name

          The imperative prototype reused an existing provider, so this used to be silent sharing; under CloudFormation the second stack fails with AlreadyExists. A hard failure is the better default, but it is a behaviour change and it makes a same-region two-target project undeployable.

          Needs either a naming strategy (project- or target-scoped names, which changes what users pass to agentcore identity ...) or CFN resource import for adoption. The L3's ExternallyManagedStateSchema covers only customJwtAuthorizer and vpcConfig, so there is no existing escape hatch.

          2. Changing a vendor is a replacement into a name collision

          CredentialProviderVendor is create-only on OAuth2 and Payment, and Name is create-only too. Changing a credential's vendor while keeping its name makes CloudFormation replace the resource with one claiming a name still held by the resource being replaced. Needs a guard that refuses the edit with an actionable message.

          3. Unverified: does deleting a stack destroy a customer-owned secret?

          The delete handler lists secretsmanager:DeleteSecret unconditionally. If that applies to an EXTERNAL secret, deleting a stack destroys a secret the customer owns and manages elsewhere. Still untested.

          This is now cheap to test: deploy a project with a secretRef credential, delete the stack, and check whether the referenced secret survives. Worth doing before refactor ships — it is the highest-severity unknown left here.

          4. CredentialNameSchema.min(3)

          Still a workaround for the pinned L3 rejecting shorter names at build (src/projectSchemas/credential.ts). Since we maintain the L3, align it and drop back to 1.

          Moved out

          Payment credential providers are tracked in #2095. project deploy refuses them with an explicit error rather than half-creating one; that issue covers both the Quick Create path (needs no provider, already released in L3 alpha.49 via L3 #324) and the Manual path's schema gap.

          Reference, still accurate

          • CloudFormation support: ApiKeyCredentialProvider, OAuth2CredentialProvider and PaymentCredentialProvider are all LIVE and FULLY_MUTABLE in the us-east-1 registry. On AWS::BedrockAgentCore::ApiKeyCredentialProvider: readOnlyProperties include CredentialProviderArn and ApiKeySecretArn; writeOnlyProperties are ApiKey, ApiKeySecretConfig, ApiKeySecretSource; createOnlyProperties is Name.
          • The L3 tolerates a CDK token for a credential ARN. CredentialDeployedStateSchema.credentialProviderArn is a bare z.string(), and all consumption sites pass it straight into a CFN property or a truthiness check — no .split/.match/.slice/.startsWith/.replace on a credential ARN anywhere. IAM grants are built from the credential name plus partition/region/account, never by parsing the ARN. So no L3 schema change was needed.
          • OAuth2CredentialProvider differs in shape: ClientSecretSource is read-only at the top level, so the secret config lives insideOauth2ProviderConfigInput. All 9 vendor configs support ClientSecretConfig + ClientSecretSource.
          • CustomOauth2ProviderConfigInput requires OauthDiscovery and has no Scopes field, which is why a spec's scopes are consumed where the credential is used, not where the provider is made.

          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

            , 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' credential provider follow-ups: name collisions, vendor change, and an untested secret-delete risk · Issue #2093 · aws/agentcore-cli · GitHub
            Skip to content

            credential provider follow-ups: name collisions, vendor change, and an untested secret-delete risk #2093

            Description

            @notgitika

            Status

            The drop bug is fixed by #2089, which creates credential providers in CloudFormation. Two of the six open questions below are answered; four are still open. This issue now tracks those four.

            The original bug

            On the refactor branch, agentcore project deploy silently ignored any credential declared in agentcore.json — the provider was never created and the deploy reported success:

            $ agentcore project add credentials api-key --name openai-key
            $ agentcore project deploy
            ...
            Deployed project 'Example' to target 'default' # exit 0
            $ agentcore identity api-key-credential-provider get --name openai-key
            Error: ApiKeyCredentialProvider not found for openai-key
            

            The vended CDK app expected provider ARNs to already be recorded in agentcore/.cli/deployed-state.json (src/assets/cdk/bin/cdk.ts, read inside a try/catch that tolerated absence). Nothing in the deploy path wrote that file, so the credentials map was always undefined and every declaration fell on the floor — silently, because the read tolerated a missing file.

            Not in main; refactor only.

            How #2089 fixes it

            src/assets/cdk/lib/cdk-stack.ts creates a provider for each declared credential using the constructs from L3 #333. Payment connectors now carry the credential's name and the stack resolves it to the ARN of the provider it created, which removed the last reader of deployed-state.json.

            Answered: .env.local secrets are neither plaintext nor EXTERNAL

            The original framing offered two options and both were bad. There is a third, which is what L3 #333 built for: the provider is created with CREDENTIAL_SECRET_PLACEHOLDER, and the CLI replaces it over the Identity API once the deploy succeeds. Real secret material never enters the template. A credential with a secretRef/clientSecretRef deploys as EXTERNAL and needs no sync.

            One correction to the L3's own docstring, which suggests detecting an unsynced provider by finding the placeholder still in place: that is not implementable. ApiKey is a CloudFormation write-only property and GetApiKeyCredentialProvider returns only apiKeySecretArn — there is nothing to read back and compare. #2089 therefore syncs unconditionally on every deploy, at the cost of a new secret version each time.

            Answered: secret rotation

            Moot under the placeholder design. The template never carries the real key, so a stack update cannot overwrite a rotated secret with a stale one. If CloudFormation does update the resource for some other reason, it writes the placeholder and the post-deploy sync immediately replaces it.

            Still open

            1. Provider names are not scoped to a project, so stacks collide

            AgentCoreApiKeyCredentialProvider sets the CFN Name to the bare credential name (AgentCoreCredentialProvider.ts:47, :116) — projectName only feeds tags. Names are unique per (account, region, token vault), so:

            • two projects in one account+region both declaring openai-key collide
            • two targets of one project in the same region collide — each target gets its own stack, and both try to create the same provider name

            The imperative prototype reused an existing provider, so this used to be silent sharing; under CloudFormation the second stack fails with AlreadyExists. A hard failure is the better default, but it is a behaviour change and it makes a same-region two-target project undeployable.

            Needs either a naming strategy (project- or target-scoped names, which changes what users pass to agentcore identity ...) or CFN resource import for adoption. The L3's ExternallyManagedStateSchema covers only customJwtAuthorizer and vpcConfig, so there is no existing escape hatch.

            2. Changing a vendor is a replacement into a name collision

            CredentialProviderVendor is create-only on OAuth2 and Payment, and Name is create-only too. Changing a credential's vendor while keeping its name makes CloudFormation replace the resource with one claiming a name still held by the resource being replaced. Needs a guard that refuses the edit with an actionable message.

            3. Unverified: does deleting a stack destroy a customer-owned secret?

            The delete handler lists secretsmanager:DeleteSecret unconditionally. If that applies to an EXTERNAL secret, deleting a stack destroys a secret the customer owns and manages elsewhere. Still untested.

            This is now cheap to test: deploy a project with a secretRef credential, delete the stack, and check whether the referenced secret survives. Worth doing before refactor ships — it is the highest-severity unknown left here.

            4. CredentialNameSchema.min(3)

            Still a workaround for the pinned L3 rejecting shorter names at build (src/projectSchemas/credential.ts). Since we maintain the L3, align it and drop back to 1.

            Moved out

            Payment credential providers are tracked in #2095. project deploy refuses them with an explicit error rather than half-creating one; that issue covers both the Quick Create path (needs no provider, already released in L3 alpha.49 via L3 #324) and the Manual path's schema gap.

            Reference, still accurate

            • CloudFormation support: ApiKeyCredentialProvider, OAuth2CredentialProvider and PaymentCredentialProvider are all LIVE and FULLY_MUTABLE in the us-east-1 registry. On AWS::BedrockAgentCore::ApiKeyCredentialProvider: readOnlyProperties include CredentialProviderArn and ApiKeySecretArn; writeOnlyProperties are ApiKey, ApiKeySecretConfig, ApiKeySecretSource; createOnlyProperties is Name.
            • The L3 tolerates a CDK token for a credential ARN. CredentialDeployedStateSchema.credentialProviderArn is a bare z.string(), and all consumption sites pass it straight into a CFN property or a truthiness check — no .split/.match/.slice/.startsWith/.replace on a credential ARN anywhere. IAM grants are built from the credential name plus partition/region/account, never by parsing the ARN. So no L3 schema change was needed.
            • OAuth2CredentialProvider differs in shape: ClientSecretSource is read-only at the top level, so the secret config lives insideOauth2ProviderConfigInput. All 9 vendor configs support ClientSecretConfig + ClientSecretSource.
            • CustomOauth2ProviderConfigInput requires OauthDiscovery and has no Scopes field, which is why a spec's scopes are consumed where the credential is used, not where the provider is made.

            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

              , 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); credential provider follow-ups: name collisions, vendor change, and an untested secret-delete risk · Issue #2093 · aws/agentcore-cli · GitHub
              Skip to content

              credential provider follow-ups: name collisions, vendor change, and an untested secret-delete risk #2093

              Description

              @notgitika

              Status

              The drop bug is fixed by #2089, which creates credential providers in CloudFormation. Two of the six open questions below are answered; four are still open. This issue now tracks those four.

              The original bug

              On the refactor branch, agentcore project deploy silently ignored any credential declared in agentcore.json — the provider was never created and the deploy reported success:

              $ agentcore project add credentials api-key --name openai-key
              $ agentcore project deploy
              ...
              Deployed project 'Example' to target 'default' # exit 0
              $ agentcore identity api-key-credential-provider get --name openai-key
              Error: ApiKeyCredentialProvider not found for openai-key
              

              The vended CDK app expected provider ARNs to already be recorded in agentcore/.cli/deployed-state.json (src/assets/cdk/bin/cdk.ts, read inside a try/catch that tolerated absence). Nothing in the deploy path wrote that file, so the credentials map was always undefined and every declaration fell on the floor — silently, because the read tolerated a missing file.

              Not in main; refactor only.

              How #2089 fixes it

              src/assets/cdk/lib/cdk-stack.ts creates a provider for each declared credential using the constructs from L3 #333. Payment connectors now carry the credential's name and the stack resolves it to the ARN of the provider it created, which removed the last reader of deployed-state.json.

              Answered: .env.local secrets are neither plaintext nor EXTERNAL

              The original framing offered two options and both were bad. There is a third, which is what L3 #333 built for: the provider is created with CREDENTIAL_SECRET_PLACEHOLDER, and the CLI replaces it over the Identity API once the deploy succeeds. Real secret material never enters the template. A credential with a secretRef/clientSecretRef deploys as EXTERNAL and needs no sync.

              One correction to the L3's own docstring, which suggests detecting an unsynced provider by finding the placeholder still in place: that is not implementable. ApiKey is a CloudFormation write-only property and GetApiKeyCredentialProvider returns only apiKeySecretArn — there is nothing to read back and compare. #2089 therefore syncs unconditionally on every deploy, at the cost of a new secret version each time.

              Answered: secret rotation

              Moot under the placeholder design. The template never carries the real key, so a stack update cannot overwrite a rotated secret with a stale one. If CloudFormation does update the resource for some other reason, it writes the placeholder and the post-deploy sync immediately replaces it.

              Still open

              1. Provider names are not scoped to a project, so stacks collide

              AgentCoreApiKeyCredentialProvider sets the CFN Name to the bare credential name (AgentCoreCredentialProvider.ts:47, :116) — projectName only feeds tags. Names are unique per (account, region, token vault), so:

              • two projects in one account+region both declaring openai-key collide
              • two targets of one project in the same region collide — each target gets its own stack, and both try to create the same provider name

              The imperative prototype reused an existing provider, so this used to be silent sharing; under CloudFormation the second stack fails with AlreadyExists. A hard failure is the better default, but it is a behaviour change and it makes a same-region two-target project undeployable.

              Needs either a naming strategy (project- or target-scoped names, which changes what users pass to agentcore identity ...) or CFN resource import for adoption. The L3's ExternallyManagedStateSchema covers only customJwtAuthorizer and vpcConfig, so there is no existing escape hatch.

              2. Changing a vendor is a replacement into a name collision

              CredentialProviderVendor is create-only on OAuth2 and Payment, and Name is create-only too. Changing a credential's vendor while keeping its name makes CloudFormation replace the resource with one claiming a name still held by the resource being replaced. Needs a guard that refuses the edit with an actionable message.

              3. Unverified: does deleting a stack destroy a customer-owned secret?

              The delete handler lists secretsmanager:DeleteSecret unconditionally. If that applies to an EXTERNAL secret, deleting a stack destroys a secret the customer owns and manages elsewhere. Still untested.

              This is now cheap to test: deploy a project with a secretRef credential, delete the stack, and check whether the referenced secret survives. Worth doing before refactor ships — it is the highest-severity unknown left here.

              4. CredentialNameSchema.min(3)

              Still a workaround for the pinned L3 rejecting shorter names at build (src/projectSchemas/credential.ts). Since we maintain the L3, align it and drop back to 1.

              Moved out

              Payment credential providers are tracked in #2095. project deploy refuses them with an explicit error rather than half-creating one; that issue covers both the Quick Create path (needs no provider, already released in L3 alpha.49 via L3 #324) and the Manual path's schema gap.

              Reference, still accurate

              • CloudFormation support: ApiKeyCredentialProvider, OAuth2CredentialProvider and PaymentCredentialProvider are all LIVE and FULLY_MUTABLE in the us-east-1 registry. On AWS::BedrockAgentCore::ApiKeyCredentialProvider: readOnlyProperties include CredentialProviderArn and ApiKeySecretArn; writeOnlyProperties are ApiKey, ApiKeySecretConfig, ApiKeySecretSource; createOnlyProperties is Name.
              • The L3 tolerates a CDK token for a credential ARN. CredentialDeployedStateSchema.credentialProviderArn is a bare z.string(), and all consumption sites pass it straight into a CFN property or a truthiness check — no .split/.match/.slice/.startsWith/.replace on a credential ARN anywhere. IAM grants are built from the credential name plus partition/region/account, never by parsing the ARN. So no L3 schema change was needed.
              • OAuth2CredentialProvider differs in shape: ClientSecretSource is read-only at the top level, so the secret config lives insideOauth2ProviderConfigInput. All 9 vendor configs support ClientSecretConfig + ClientSecretSource.
              • CustomOauth2ProviderConfigInput requires OauthDiscovery and has no Scopes field, which is why a spec's scopes are consumed where the credential is used, not where the provider is made.

              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