

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 版本存留期的閘道套件
<a name="package-version-age-gating"></a>

**重要**  
版本存留期閘道無法保護已通過隔離時段的現有版本中發現的漏洞。將 CodeArtifact 與 Amazon Inspector 搭配使用，以漏洞掃描已建立的套件。

當套件版本發佈至公有登錄檔時，登錄檔會記錄發佈時間戳記。CodeArtifact 會在從外部連線快取套件時保留這些原始上游時間戳記。您可以在 CI/CD 管道中讀取這些時間戳記，並拒絕過早發佈的版本。

大多數惡意套件都會在發佈後數小時內偵測到並移除。3 到 7 天的隔離時段會封鎖大多數供應鏈攻擊，包括相依性混淆、維護者帳戶接管和錯別字。

**注意**  
本主題中的範例會查詢 CodeArtifact 儲存庫 URL，這是套件管理員用來下載套件的相同端點。所有請求都需要身分驗證。在 `Authorization: Bearer`標頭中傳遞有效的 CodeArtifact 授權字符。若要取得權杖，請執行 `aws codeartifact get-authorization-token`（請參閱本主題稍後的 CI/CD 範例，了解使用 `--domain`和 的完整調用`--domain-owner`)。  
請勿使用 傳回`publishedTime`的欄位`aws codeartifact describe-package-version`進行年齡調整。此欄位會在擷取時間填入。對於未擷取原始上游發佈時間的記錄 （包括引入每個成品時間戳記擷取之前擷取的套件，以及某些擷取路徑），它會無提示地回到記錄的上次更新時間。API 回應不會指出您正在讀取的兩個值中，因此您無法將其視為上游發佈日期。請改用本主題中描述的每個格式欄位。

每個格式的詳細資訊不同，但每種格式的閘道模式都相同：讀取每個已解析版本的上游發佈時間戳記、將其與隔離截止值 （目前時間減去您選擇的時段） 進行比較，如果任何版本比截止值更新，則組建會失敗。如果您使用多個套件格式，您可以使用如下所示的格式特定欄位套用相同的模式。

## npm
<a name="package-version-age-gating-npm"></a>

CodeArtifact 會傳回 npm 封裝`time`欄位中每個版本的原始 npmjs.org 發佈時間戳記。許多套件管理員和安全工具會以原生方式讀取此欄位。這些選項包括 Yarn 4 `npmMinimalAgeGate`選項、Renovate `minimumReleaseAge`設定和 pnpm `minimumReleaseAge`設定，所有這些都適用於 CodeArtifact，無需修改。

**要求:**

```
curl -H "Authorization: Bearer $CODEARTIFACT_AUTH_TOKEN" \
  https://my-domain-111122223333.d.codeartifact.us-east-1.amazonaws.com/npm/my-repo/axios
```

**回應 （縮寫）：**

```
{
  "name": "axios",
  "time": {
    "1.6.0": "2023-10-26T21:15:55.685Z",
    "1.6.5": "2024-01-05T19:52:15.051Z",
    "1.7.0": "2024-05-19T20:25:03.615Z",
    "1.7.1": "2024-05-20T13:32:52.757Z",
    "1.7.2": "2024-05-21T16:58:04.163Z"
  }
}
```

**金鑰欄位：**`time["<version>"]`— 值是來自 npmjs.org 的原始發佈日期。例如， `time["1.7.2"]`會傳回 `2024-05-21T16:58:04.163Z`，這是發佈該版本時 npmjs.org 記錄的確切時間戳記。若要在存留期時進入閘道，請將此值與目前時間減去隔離時段進行比較。

## Maven
<a name="package-version-age-gating-maven"></a>

對於 Maven 成品，CodeArtifact 會在您下載檔案 (JAR、POM 或其他成品） 時，傳回 `Last-Modified` HTTP 回應標頭中的原始 Maven Central 發佈時間戳記。

**要求:**

```
curl -I -H "Authorization: Bearer $CODEARTIFACT_AUTH_TOKEN" \
  https://my-domain-111122223333.d.codeartifact.us-east-1.amazonaws.com/maven/my-repo/\
org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.pom
```

**回應標頭：**

```
HTTP/2 200
content-type: application/xml
Last-Modified: Fri, 26 Feb 2021 20:40:52 GMT
content-length: 22672
```

**金鑰欄位：**`Last-Modified`標頭 — 這是 Maven Central 的原始發佈日期。將其與您的隔離截止值進行比較，以判斷成品是否太新。

**注意**  
內的 `<lastUpdated>` 欄位`maven-metadata.xml`反映 CodeArtifact 上次重新整理其快取的時間，而不是上游發佈日期。請勿使用`maven-metadata.xml`時間戳記進行年齡調整。

## NuGet
<a name="package-version-age-gating-nuget"></a>

對於 NuGet 套件，CodeArtifact 會在 V3 註冊回應的 `published` 欄位中傳回原始 nuget.org 發佈時間戳記。

**要求:**

```
curl -H "Authorization: Bearer $CODEARTIFACT_AUTH_TOKEN" \
  https://my-domain-111122223333.d.codeartifact.us-east-1.amazonaws.com/nuget/my-repo/\
v3/registration4/newtonsoft.json/index.json
```

**回應 （已縮寫，顯示一個版本）：**

```
{
  "items": [{
    "items": [{
      "catalogEntry": {
        "id": "Newtonsoft.Json",
        "version": "13.0.3",
        "published": "2023-03-08T07:42:54.647+00:00",
        "listed": true
      }
    }]
  }]
}
```

**金鑰欄位：**`catalogEntry.published`— 這是來自 nuget.org 的原始發佈日期。例如， `"2023-03-08T07:42:54.647+00:00"`表示 Newtonsoft.Json 13.0.3 已於 2023 年 3 月 8 日發佈。

## Cargo (Rust)
<a name="package-version-age-gating-cargo"></a>

對於從 crates.io 提取的貨運箱，CodeArtifact 會在 crates.io V1 API `versions`回應中保留原始發佈時間戳記。查詢 V1 API 端點以取得木箱，以讀取每個版本的`created_at`時間戳記。

**要求:**

```
curl -H "Authorization: Bearer $CODEARTIFACT_AUTH_TOKEN" \
  https://my-domain-111122223333.d.codeartifact.us-east-1.amazonaws.com/cargo/my-repo/\
api/v1/crates/serde/versions
```

**金鑰欄位：**V1 `versions`回應中的每個版本`created_at`時間戳記是原始 crates.io 發佈日期。

**注意**  
Cargo 使用稀疏索引通訊協定 (`<prefix>/<crate>`) 進行正常相依性解析。稀疏索引會傳回`features`每個版本的 `vers`、`yanked`、 `cksum`和 ，但不包含發佈時間戳記。若要對存留期的貨物相依性進行閘道，請在管道中的單獨步驟中查詢上面顯示的 V1 API。標準 `cargo build`和 `cargo update`命令不會呼叫 V1 API。

## PyPI
<a name="package-version-age-gating-pypi"></a>

對於 Python 套件，CodeArtifact 支援 PEP 691 (JSON Simple API) 和 PEP 700，其中包含每個分佈檔案的 `upload-time` 欄位。

**要求:**

```
curl -H "Authorization: Bearer $CODEARTIFACT_AUTH_TOKEN" \
  -H "Accept: application/vnd.pypi.simple.v1+json" \
  https://my-domain-111122223333.d.codeartifact.us-east-1.amazonaws.com/pypi/my-repo/simple/requests/
```

**回應 （縮寫）：**

```
{
  "files": [{
    "filename": "requests-2.31.0-py3-none-any.whl",
    "upload-time": "2023-05-22T15:12:42.313790Z",
    "size": 62574,
    "url": "../../packages/requests/2.31.0/requests-2.31.0-py3-none-any.whl"
  }]
}
```

**金鑰欄位：**`upload-time`— 這是 PyPI 的原始發佈日期。等工具原生`uv`支援此項目：

```
# Only install versions published before a specific date
uv pip install --exclude-newer 2026-05-01T00:00:00Z -r requirements.txt
```

**注意**  
PEP 691 JSON API 需要 `Accept: application/vnd.pypi.simple.v1+json`標頭。預設 HTML Simple API 不包含時間戳記。  
對於引入每個成品上傳時間擷取之前擷取的分發檔案 （舊版記錄）， `upload-time` 欄位可能沒有 中的項目`files[]`。您的門控邏輯應將遺失視為無法驗證`upload-time`的版本，並根據您的政策決定是否允許或封鎖它。

## 範例：在 CI/CD 中閘道 npm 套件
<a name="package-version-age-gating-example"></a>

下列 Python 指令碼會讀取`package-lock.json`檔案、查詢 CodeArtifact 以取得每個相依性的發佈時間戳記，並在隔離視窗中發佈任何版本時以非零狀態結束。

```
#!/usr/bin/env python3
"""
gate_package_age.py - Block npm packages published too recently.

Reads package-lock.json, queries CodeArtifact for each dependency's
publish timestamp, and rejects versions newer than QUARANTINE_HOURS.

Environment variables:
  CODEARTIFACT_AUTH_TOKEN  - from `aws codeartifact get-authorization-token`
  CODEARTIFACT_NPM_ENDPOINT - from `aws codeartifact get-repository-endpoint`
  QUARANTINE_HOURS - minimum version age in hours (default: 72)
"""
import json, os, sys, urllib.request
from datetime import datetime, timezone, timedelta

def get_publish_time(endpoint, token, package, version):
    """Fetch the publish timestamp for a specific version from CodeArtifact."""
    url = f"{endpoint.rstrip('/')}/{package}"
    req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
    data = json.loads(urllib.request.urlopen(req, timeout=30).read())
    time_str = data.get("time", {}).get(version)
    if not time_str:
        return None
    return datetime.fromisoformat(time_str.replace("Z", "+00:00"))

token = os.environ["CODEARTIFACT_AUTH_TOKEN"]
endpoint = os.environ["CODEARTIFACT_NPM_ENDPOINT"]
quarantine_hours = int(os.environ.get("QUARANTINE_HOURS", "72"))
cutoff = datetime.now(timezone.utc) - timedelta(hours=quarantine_hours)

with open("package-lock.json") as f:
    lockfile = json.load(f)

blocked = []
checked = 0
for path, info in lockfile.get("packages", {}).items():
    if not path.startswith("node_modules/"):
        continue
    name = path.replace("node_modules/", "", 1)
    version = info.get("version")
    if not name or not version:
        continue
    checked += 1
    pub_time = get_publish_time(endpoint, token, name, version)
    if pub_time and pub_time > cutoff:
        age_h = (datetime.now(timezone.utc) - pub_time).total_seconds() / 3600
        blocked.append(f"  {name}@{version} (age: {age_h:.0f}h, published {pub_time.isoformat()})")

if blocked:
    print(f"BLOCKED: {len(blocked)} package(s) newer than {quarantine_hours} hours:")
    print("\n".join(blocked))
    sys.exit(1)
print(f"PASSED: all {checked} packages older than {quarantine_hours} hours.")
```

**CI/CD 整合：**

```
# Step 1: Authenticate
export CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token \
  --domain my-domain --domain-owner 111122223333 \
  --query authorizationToken --output text)

export CODEARTIFACT_NPM_ENDPOINT=$(aws codeartifact get-repository-endpoint \
  --domain my-domain --domain-owner 111122223333 \
  --repository my-repo --format npm \
  --query repositoryEndpoint --output text)

# Step 2: Set quarantine window (72 hours = 3 days)
export QUARANTINE_HOURS=72

# Step 3: Generate lockfile without installing
npm install --package-lock-only

# Step 4: Gate on age — fails if any dependency is too new
python3 gate_package_age.py

# Step 5: If gate passes, install
npm ci
```

**套件遭到封鎖時的輸出範例：**

```
BLOCKED: 1 package(s) newer than 72 hours:
  evil-package@1.0.0 (age: 2h, published 2026-06-03T08:30:00+00:00)
```

**所有套件通過時的範例輸出：**

```
PASSED: all 847 packages older than 72 hours.
```

## 已緩解的攻擊
<a name="package-version-age-gating-attacks"></a>


| 攻擊類型 | 運作方式 | 年齡調控如何有所幫助 | 
| --- | --- | --- | 
| 相依性混淆 | 攻擊者會將與內部套件同名的高版本套件發佈至公有登錄檔。 | 惡意版本是全新的。被隔離時段封鎖。 | 
| 帳戶接管 | 攻擊者入侵維護者的登入資料並發佈惡意更新 （例如，事件串流）。 | 保留新版本。社群在隔離期間偵測並還原它。 | 
| Typosquatting | 攻擊者會發佈名稱類似於熱門套件的套件 （例如，lodashs而非 lodash)。 | 所有版本的 typosquat 套件都是新的。每個版本都會遭到封鎖。 | 
| Star-jacking | 攻擊者將惡意軟體注入其控制之合法套件的修補程式版本中。 | 惡意修補程式是新發佈的版本。封鎖，直到超過隔離時段為止。 | 

## 支援的格式摘要
<a name="package-version-age-gating-summary"></a>


| 格式 | 在何處讀取發佈時間戳記 | 金鑰欄位 | 狀態 | 
| --- | --- | --- | --- | 
| npm | 套件回應 (GET /<package>) | time["<version>"] | Available | 
| Maven | 成品下載時的 HTTP 回應標頭 | Last-Modified | Available | 
| NuGet | V3 註冊索引 | catalogEntry.published | Available | 
| 貨物 | V1 API 版本回應 | created\_at | Available | 
| PyPI | PEP 691 JSON Simple API | upload-time | Available | 