teamcopilot 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/frontend/assets/{cssMode-Ce-tv-Z5.js → cssMode-DXQJFMVx.js} +1 -1
- package/dist/frontend/assets/{freemarker2-C2lknTn7.js → freemarker2-BW1EZI_f.js} +1 -1
- package/dist/frontend/assets/{handlebars-BJhjXjD9.js → handlebars-Biheo7KR.js} +1 -1
- package/dist/frontend/assets/{html-DJYWktbJ.js → html-BH01Jifq.js} +1 -1
- package/dist/frontend/assets/{htmlMode-BhO7-ZMs.js → htmlMode-BhB6cjl6.js} +1 -1
- package/dist/frontend/assets/{index-Vn_YLpmv.js → index-Dl290_nP.js} +3 -3
- package/dist/frontend/assets/{javascript-bs3H2OkW.js → javascript-vRQ7Z-SD.js} +1 -1
- package/dist/frontend/assets/{jsonMode-Buh5rEfM.js → jsonMode-CQXvACVS.js} +1 -1
- package/dist/frontend/assets/{liquid-DtJ4QQui.js → liquid-H0P3dWK2.js} +1 -1
- package/dist/frontend/assets/{mdx-BwrY1-Zp.js → mdx-Bz0q5Fh9.js} +1 -1
- package/dist/frontend/assets/{python-CuS1hmPy.js → python-v4fpwY3A.js} +1 -1
- package/dist/frontend/assets/{razor-BfkLSycQ.js → razor-ptFSsABU.js} +1 -1
- package/dist/frontend/assets/{tsMode-DRoUZkPV.js → tsMode-DgGhJJ5C.js} +1 -1
- package/dist/frontend/assets/{typescript-DhEmRcWh.js → typescript-C7Kjd36G.js} +1 -1
- package/dist/frontend/assets/{xml-DDVpxsXN.js → xml-BBvPtCBs.js} +1 -1
- package/dist/frontend/assets/{yaml-CKuuQ7TI.js → yaml-dCpIE0um.js} +1 -1
- package/dist/frontend/index.html +1 -1
- package/dist/workspace_files/.opencode/plugins/secret-proxy.ts +119 -7
- package/dist/workspace_files/AGENTS.md +4 -3
- package/package.json +1 -1
|
@@ -324,6 +324,12 @@ function isCurlExecutableToken(rawToken: string): boolean {
|
|
|
324
324
|
return base === "curl"
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
function isGitExecutableToken(rawToken: string): boolean {
|
|
328
|
+
const { inner } = unwrapToken(rawToken)
|
|
329
|
+
const base = inner.split("/").pop() ?? inner
|
|
330
|
+
return base === "git"
|
|
331
|
+
}
|
|
332
|
+
|
|
327
333
|
function getLongOptionName(inner: string): string | null {
|
|
328
334
|
const eqIndex = inner.indexOf("=")
|
|
329
335
|
const optionName = eqIndex === -1 ? inner : inner.slice(0, eqIndex)
|
|
@@ -546,7 +552,12 @@ export const SecretProxyPlugin: Plugin = async ({ client }) => {
|
|
|
546
552
|
return cached
|
|
547
553
|
}
|
|
548
554
|
|
|
549
|
-
const
|
|
555
|
+
const curlRewritten = substitutePlaceholdersInCurlShellString(text)
|
|
556
|
+
const gitRewritten = substitutePlaceholdersInGitShellString(curlRewritten.rewritten)
|
|
557
|
+
const rewritten = {
|
|
558
|
+
rewritten: gitRewritten.rewritten,
|
|
559
|
+
referencedKeys: Array.from(new Set([...curlRewritten.referencedKeys, ...gitRewritten.referencedKeys])).sort(),
|
|
560
|
+
}
|
|
550
561
|
cache.set(text, rewritten)
|
|
551
562
|
return rewritten
|
|
552
563
|
}
|
|
@@ -564,6 +575,27 @@ export const SecretProxyPlugin: Plugin = async ({ client }) => {
|
|
|
564
575
|
}
|
|
565
576
|
}
|
|
566
577
|
|
|
578
|
+
function rewriteGitRawToken(rawToken: string): { rewritten: string; referencedKeys: string[] } {
|
|
579
|
+
const { quote, inner } = unwrapToken(rawToken)
|
|
580
|
+
const { rewritten, referencedKeys } = rewriteTokenInner(inner)
|
|
581
|
+
if (referencedKeys.length === 0) {
|
|
582
|
+
return {
|
|
583
|
+
rewritten: rawToken,
|
|
584
|
+
referencedKeys,
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (quote === null && rawToken.includes("'")) {
|
|
588
|
+
return {
|
|
589
|
+
rewritten: `"${escapeForDoubleQuotedShell(rewritten.replace(/'/g, ""))}"`,
|
|
590
|
+
referencedKeys,
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return {
|
|
594
|
+
rewritten: wrapTokenForShellExpansion(quote, rewritten, true),
|
|
595
|
+
referencedKeys,
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
567
599
|
function rewriteRawTokenValuePortion(rawToken: string): { rewritten: string; referencedKeys: string[] } {
|
|
568
600
|
const { quote, inner } = unwrapToken(rawToken)
|
|
569
601
|
const eqIndex = inner.indexOf("=")
|
|
@@ -760,6 +792,82 @@ export const SecretProxyPlugin: Plugin = async ({ client }) => {
|
|
|
760
792
|
}
|
|
761
793
|
}
|
|
762
794
|
|
|
795
|
+
function substitutePlaceholdersInGitShellString(
|
|
796
|
+
text: string,
|
|
797
|
+
): { rewritten: string; referencedKeys: string[] } {
|
|
798
|
+
const rawTokens = tokenizeCommand(text)
|
|
799
|
+
if (rawTokens.length === 0) {
|
|
800
|
+
return {
|
|
801
|
+
rewritten: text,
|
|
802
|
+
referencedKeys: [],
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const replacements: Array<{ start: number; end: number; text: string }> = []
|
|
807
|
+
const referencedKeys = new Set<string>()
|
|
808
|
+
let mutated = false
|
|
809
|
+
let atCommandStart = true
|
|
810
|
+
|
|
811
|
+
for (let index = 0; index < rawTokens.length; index += 1) {
|
|
812
|
+
const rawToken = rawTokens[index]
|
|
813
|
+
|
|
814
|
+
if (SHELL_CONTROL_TOKENS.has(rawToken.raw)) {
|
|
815
|
+
atCommandStart = true
|
|
816
|
+
continue
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
if (!atCommandStart) {
|
|
820
|
+
continue
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (!isGitExecutableToken(rawToken.raw)) {
|
|
824
|
+
atCommandStart = false
|
|
825
|
+
continue
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
atCommandStart = false
|
|
829
|
+
for (let j = index + 1; j < rawTokens.length; j += 1) {
|
|
830
|
+
const segmentToken = rawTokens[j]
|
|
831
|
+
if (SHELL_CONTROL_TOKENS.has(segmentToken.raw)) {
|
|
832
|
+
atCommandStart = true
|
|
833
|
+
index = j - 1
|
|
834
|
+
break
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const rewritten = rewriteGitRawToken(segmentToken.raw)
|
|
838
|
+
if (rewritten.rewritten !== segmentToken.raw) {
|
|
839
|
+
replacements.push({
|
|
840
|
+
start: segmentToken.start,
|
|
841
|
+
end: segmentToken.end,
|
|
842
|
+
text: rewritten.rewritten,
|
|
843
|
+
})
|
|
844
|
+
mutated = true
|
|
845
|
+
}
|
|
846
|
+
for (const key of rewritten.referencedKeys) {
|
|
847
|
+
referencedKeys.add(key)
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
let rewrittenText = text
|
|
853
|
+
if (mutated) {
|
|
854
|
+
const output: string[] = []
|
|
855
|
+
let cursor = 0
|
|
856
|
+
for (const replacement of replacements.sort((left, right) => left.start - right.start)) {
|
|
857
|
+
output.push(text.slice(cursor, replacement.start))
|
|
858
|
+
output.push(replacement.text)
|
|
859
|
+
cursor = replacement.end
|
|
860
|
+
}
|
|
861
|
+
output.push(text.slice(cursor))
|
|
862
|
+
rewrittenText = output.join("")
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
return {
|
|
866
|
+
rewritten: rewrittenText,
|
|
867
|
+
referencedKeys: Array.from(referencedKeys).sort(),
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
763
871
|
return {
|
|
764
872
|
"command.execute.before": async (input) => {
|
|
765
873
|
const sessionID = typeof input.sessionID === "string" ? input.sessionID.trim() : ""
|
|
@@ -772,11 +880,13 @@ export const SecretProxyPlugin: Plugin = async ({ client }) => {
|
|
|
772
880
|
|
|
773
881
|
const commandCache = new Map<string, { rewritten: string; referencedKeys: string[] }>()
|
|
774
882
|
if (typeof input.command === "string" && input.command.includes("{{SECRET:")) {
|
|
775
|
-
if (isCurlExecutableToken(input.command) && typeof input.arguments === "string") {
|
|
776
|
-
const
|
|
883
|
+
if ((isCurlExecutableToken(input.command) || isGitExecutableToken(input.command)) && typeof input.arguments === "string") {
|
|
884
|
+
const fullCommand = input.arguments.trim().length > 0
|
|
777
885
|
? `${input.command} ${input.arguments}`
|
|
778
886
|
: input.command
|
|
779
|
-
const rewritten =
|
|
887
|
+
const rewritten = isCurlExecutableToken(input.command)
|
|
888
|
+
? substitutePlaceholdersInCurlShellString(fullCommand)
|
|
889
|
+
: substitutePlaceholdersInGitShellString(fullCommand)
|
|
780
890
|
const prefix = `${input.command} `
|
|
781
891
|
if (rewritten.rewritten.startsWith(prefix)) {
|
|
782
892
|
input.arguments = rewritten.rewritten.slice(prefix.length)
|
|
@@ -790,11 +900,13 @@ export const SecretProxyPlugin: Plugin = async ({ client }) => {
|
|
|
790
900
|
}
|
|
791
901
|
}
|
|
792
902
|
if (typeof input.arguments === "string" && input.arguments.includes("{{SECRET:")) {
|
|
793
|
-
if (isCurlExecutableToken(input.command)) {
|
|
794
|
-
const
|
|
903
|
+
if (isCurlExecutableToken(input.command) || isGitExecutableToken(input.command)) {
|
|
904
|
+
const fullCommand = input.arguments.trim().length > 0
|
|
795
905
|
? `${input.command} ${input.arguments}`
|
|
796
906
|
: input.command
|
|
797
|
-
const rewritten =
|
|
907
|
+
const rewritten = isCurlExecutableToken(input.command)
|
|
908
|
+
? substitutePlaceholdersInCurlShellString(fullCommand)
|
|
909
|
+
: substitutePlaceholdersInGitShellString(fullCommand)
|
|
798
910
|
const prefix = `${input.command} `
|
|
799
911
|
if (rewritten.rewritten.startsWith(prefix)) {
|
|
800
912
|
input.arguments = rewritten.rewritten.slice(prefix.length)
|
|
@@ -434,17 +434,18 @@ required_secrets:
|
|
|
434
434
|
```
|
|
435
435
|
- Skill bodies may contain placeholders like `{{SECRET:OPENAI_API_KEY}}`.
|
|
436
436
|
- When using secrets in bash commands, use placeholder references like `{{SECRET:OPENAI_API_KEY}}` (general form is `{{SECRET:KEY_NAME}}`).
|
|
437
|
-
- In bash, TeamCopilot
|
|
437
|
+
- In bash, TeamCopilot substitutes `{{SECRET:KEY}}` placeholders in supported top-level `curl` use cases and top-level `git` commands. Unsupported contexts are left unchanged.
|
|
438
438
|
- TeamCopilot may internally rewrite supported secret placeholders to runtime-only env references such as `${__TEAMCOPILOT_RUNTIME_SECRET_OPENAI_API_KEY}` before execution. Those `__TEAMCOPILOT_RUNTIME_SECRET_*` references are internal only.
|
|
439
439
|
- Never write `__TEAMCOPILOT_RUNTIME_SECRET_*` yourself in tool calls, scripts, or command text, even without `$` or `${}`. Agent-authored `__TEAMCOPILOT_RUNTIME_SECRET_*` references are rejected. Always use `{{SECRET:KEY}}` instead.
|
|
440
440
|
- Supported `curl` substitution contexts include:
|
|
441
441
|
- auth-like headers passed with `-H` / `--header` when the header name is an allowed auth/session header
|
|
442
442
|
- request URLs and explicit `--url` values
|
|
443
443
|
- auth/data-related curl arguments such as `--user`, `--proxy-user`, `--oauth2-bearer`, `--data*`, `--form*`, `--cookie`, `--referer`, `--user-agent`, `--proxy`, `--resolve`, and `--connect-to`
|
|
444
|
+
- Supported `git` substitution contexts include any argument in a top-level `git ...` command. This is intended for private repository operations such as `git clone`, authenticated remotes, `git -c http.extraHeader=... fetch`, commits, and pushes.
|
|
444
445
|
- Do not assume `{{SECRET:KEY}}` will resolve inside arbitrary shell text.
|
|
445
446
|
- Do not rely on placeholder substitution in `echo`, `printf`, heredocs, redirects, `tee`, `python -c`, `node -e`, `bash -lc`, `scp`, `wget`, or file-writing commands.
|
|
446
|
-
- For
|
|
447
|
-
- If the currently supported `curl` substitution cases or allowed headers are not sufficient, prefer creating (using the `createWorkflow` tool) or updating a workflow instead of trying to force secret use through bash.
|
|
447
|
+
- For secret usage outside supported `curl` and `git` commands, prefer workflow runtime env injection via `required_secrets`.
|
|
448
|
+
- If the currently supported `curl`/`git` substitution cases or allowed headers are not sufficient, prefer creating (using the `createWorkflow` tool) or updating a workflow instead of trying to force secret use through bash.
|
|
448
449
|
- If you need to write a script or multi-step automation that uses secrets, prefer creating (using the `createWorkflow` tool) or updating a workflow instead of embedding secret placeholders in shell scripts. In workflows, all declared secrets are injected into the runtime environment (no matter where or how they are used) when you call the `runWorkflow` tool. This is secure since workflow execution is gated by engineer approval.
|
|
449
450
|
- Do not try to manually replace `{{SECRET:KEY}}` with a raw value yourself.
|
|
450
451
|
- If you use a secret like `{{SECRET:KEY_NAME}}` and that secret doesn't exist in the user's profile, the tool call will fail, and then you should ask the user to add that key to their Profile Secrets before retrying.
|